@@ -25,6 +25,7 @@ import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.KeyWithUuidDto; | |||
@@ -32,24 +33,28 @@ import org.sonar.db.component.KeyWithUuidDto; | |||
import static org.sonar.db.component.ComponentDto.removeBranchAndPullRequestFromKey; | |||
/** | |||
* Cache a map of component key -> uuid in short branches that have issues with status either RESOLVED or CONFIRMED. | |||
* Cache a map of component key -> set<uuid> in sibling branches/PR that have open issues | |||
* | |||
*/ | |||
public class ShortBranchComponentsWithIssues { | |||
private final String uuid; | |||
public class SiblingComponentsWithOpenIssues { | |||
private final DbClient dbClient; | |||
private final AnalysisMetadataHolder metadataHolder; | |||
private final TreeRootHolder treeRootHolder; | |||
private Map<String, Set<String>> uuidsByKey; | |||
public ShortBranchComponentsWithIssues(TreeRootHolder treeRootHolder, DbClient dbClient) { | |||
this.uuid = treeRootHolder.getRoot().getUuid(); | |||
public SiblingComponentsWithOpenIssues(TreeRootHolder treeRootHolder, AnalysisMetadataHolder metadataHolder, DbClient dbClient) { | |||
this.treeRootHolder = treeRootHolder; | |||
this.metadataHolder = metadataHolder; | |||
this.dbClient = dbClient; | |||
} | |||
private void loadUuidsByKey() { | |||
uuidsByKey = new HashMap<>(); | |||
String currentBranchUuid = treeRootHolder.getRoot().getUuid(); | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
List<KeyWithUuidDto> components = dbClient.componentDao().selectComponentKeysHavingIssuesToMerge(dbSession, uuid); | |||
List<KeyWithUuidDto> components = dbClient.componentDao().selectAllSiblingComponentKeysHavingOpenIssues(dbSession, | |||
metadataHolder.getBranch().getMergeBranchUuid().orElse(currentBranchUuid), currentBranchUuid); | |||
for (KeyWithUuidDto dto : components) { | |||
uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid()); | |||
} |
@@ -36,7 +36,7 @@ import org.sonar.ce.task.projectanalysis.component.DbIdsRepositoryImpl; | |||
import org.sonar.ce.task.projectanalysis.component.DisabledComponentsHolderImpl; | |||
import org.sonar.ce.task.projectanalysis.component.MergeBranchComponentUuids; | |||
import org.sonar.ce.task.projectanalysis.component.ReportModulesPath; | |||
import org.sonar.ce.task.projectanalysis.component.ShortBranchComponentsWithIssues; | |||
import org.sonar.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues; | |||
import org.sonar.ce.task.projectanalysis.component.TreeRootHolderImpl; | |||
import org.sonar.ce.task.projectanalysis.dbmigration.DbMigrationModule; | |||
import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolderImpl; | |||
@@ -77,8 +77,8 @@ import org.sonar.ce.task.projectanalysis.issue.RuleRepositoryImpl; | |||
import org.sonar.ce.task.projectanalysis.issue.RuleTagsCopier; | |||
import org.sonar.ce.task.projectanalysis.issue.ScmAccountToUser; | |||
import org.sonar.ce.task.projectanalysis.issue.ScmAccountToUserLoader; | |||
import org.sonar.ce.task.projectanalysis.issue.ShortBranchIssueMerger; | |||
import org.sonar.ce.task.projectanalysis.issue.ShortBranchIssuesLoader; | |||
import org.sonar.ce.task.projectanalysis.issue.SiblingsIssueMerger; | |||
import org.sonar.ce.task.projectanalysis.issue.SiblingsIssuesLoader; | |||
import org.sonar.ce.task.projectanalysis.issue.ShortBranchOrPullRequestTrackerExecution; | |||
import org.sonar.ce.task.projectanalysis.issue.TrackerBaseInputFactory; | |||
import org.sonar.ce.task.projectanalysis.issue.TrackerExecution; | |||
@@ -197,7 +197,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop | |||
MutableTaskResultHolderImpl.class, | |||
BatchReportReaderImpl.class, | |||
MergeBranchComponentUuids.class, | |||
ShortBranchComponentsWithIssues.class, | |||
SiblingComponentsWithOpenIssues.class, | |||
// repositories | |||
LanguageRepositoryImpl.class, | |||
@@ -281,8 +281,8 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop | |||
BaseIssuesLoader.class, | |||
IssueTrackingDelegator.class, | |||
BranchPersisterImpl.class, | |||
ShortBranchIssuesLoader.class, | |||
ShortBranchIssueMerger.class, | |||
SiblingsIssuesLoader.class, | |||
SiblingsIssueMerger.class, | |||
// filemove | |||
ScoreMatrixDumperImpl.class, |
@@ -40,12 +40,12 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter { | |||
private final IssueLifecycle issueLifecycle; | |||
private final IssueVisitors issueVisitors; | |||
private final IssueTrackingDelegator issueTracking; | |||
private final ShortBranchIssueMerger issueStatusCopier; | |||
private final SiblingsIssueMerger issueStatusCopier; | |||
private final AnalysisMetadataHolder analysisMetadataHolder; | |||
private final MergeBranchComponentUuids mergeBranchComponentUuids; | |||
public IntegrateIssuesVisitor(IssueCache issueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors, | |||
AnalysisMetadataHolder analysisMetadataHolder, IssueTrackingDelegator issueTracking, ShortBranchIssueMerger issueStatusCopier, | |||
AnalysisMetadataHolder analysisMetadataHolder, IssueTrackingDelegator issueTracking, SiblingsIssueMerger issueStatusCopier, | |||
MergeBranchComponentUuids mergeBranchComponentUuids) { | |||
super(CrawlerDepthLimit.FILE, POST_ORDER); | |||
this.issueCache = issueCache; | |||
@@ -86,9 +86,7 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter { | |||
return; | |||
} | |||
if (analysisMetadataHolder.isLongLivingBranch()) { | |||
issueStatusCopier.tryMerge(component, list); | |||
} | |||
issueStatusCopier.tryMerge(component, list); | |||
for (DefaultIssue issue : list) { | |||
process(component, issue, cacheAppender); |
@@ -94,7 +94,7 @@ public class IssueLifecycle { | |||
public void mergeConfirmedOrResolvedFromShortLivingBranch(DefaultIssue raw, DefaultIssue base, String fromShortBranchName) { | |||
copyAttributesOfIssueFromOtherBranch(raw, base); | |||
raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_SHORT_BRANCH, fromShortBranchName, analysisMetadataHolder.getBranch().getName()); | |||
raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_SHORT_BRANCH, fromShortBranchName, analysisMetadataHolder.isPullRequest() ? analysisMetadataHolder.getPullRequestKey() : analysisMetadataHolder.getBranch().getName()); | |||
} | |||
private void copyAttributesOfIssueFromOtherBranch(DefaultIssue to, DefaultIssue from) { |
@@ -27,7 +27,7 @@ import org.sonar.api.rule.RuleKey; | |||
import org.sonar.core.issue.tracking.Trackable; | |||
@Immutable | |||
public class ShortBranchIssue implements Trackable { | |||
public class SiblingIssue implements Trackable { | |||
private final String key; | |||
private final Integer line; | |||
private final String message; | |||
@@ -35,9 +35,9 @@ public class ShortBranchIssue implements Trackable { | |||
private final RuleKey ruleKey; | |||
private final String status; | |||
private final String branchName; | |||
private final Date creationDate; | |||
private final Date updateDate; | |||
public ShortBranchIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName, Date creationDate) { | |||
public SiblingIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName, Date updateDate) { | |||
this.key = key; | |||
this.line = line; | |||
this.message = message; | |||
@@ -45,7 +45,7 @@ public class ShortBranchIssue implements Trackable { | |||
this.ruleKey = ruleKey; | |||
this.status = status; | |||
this.branchName = branchName; | |||
this.creationDate = creationDate; | |||
this.updateDate = updateDate; | |||
} | |||
public String getKey() { | |||
@@ -84,8 +84,8 @@ public class ShortBranchIssue implements Trackable { | |||
} | |||
@Override | |||
public Date getCreationDate() { | |||
return creationDate; | |||
public Date getUpdateDate() { | |||
return updateDate; | |||
} | |||
@Override | |||
@@ -104,7 +104,7 @@ public class ShortBranchIssue implements Trackable { | |||
if (getClass() != obj.getClass()) { | |||
return false; | |||
} | |||
ShortBranchIssue other = (ShortBranchIssue) obj; | |||
SiblingIssue other = (SiblingIssue) obj; | |||
return key.equals(other.key); | |||
} | |||
@@ -25,37 +25,36 @@ import org.sonar.ce.task.projectanalysis.component.Component; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.tracking.SimpleTracker; | |||
import org.sonar.core.issue.tracking.Tracking; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
public class ShortBranchIssueMerger { | |||
private final ShortBranchIssuesLoader shortBranchIssuesLoader; | |||
private final SimpleTracker<DefaultIssue, ShortBranchIssue> tracker; | |||
public class SiblingsIssueMerger { | |||
private final SiblingsIssuesLoader siblingsIssuesLoader; | |||
private final SimpleTracker<DefaultIssue, SiblingIssue> tracker; | |||
private final IssueLifecycle issueLifecycle; | |||
public ShortBranchIssueMerger(ShortBranchIssuesLoader resolvedShortBranchIssuesLoader, IssueLifecycle issueLifecycle) { | |||
this(resolvedShortBranchIssuesLoader, new SimpleTracker<>(), issueLifecycle); | |||
public SiblingsIssueMerger(SiblingsIssuesLoader resolvedSiblingsIssuesLoader, IssueLifecycle issueLifecycle) { | |||
this(resolvedSiblingsIssuesLoader, new SimpleTracker<>(), issueLifecycle); | |||
} | |||
public ShortBranchIssueMerger(ShortBranchIssuesLoader shortBranchIssuesLoader, SimpleTracker<DefaultIssue, ShortBranchIssue> tracker, IssueLifecycle issueLifecycle) { | |||
this.shortBranchIssuesLoader = shortBranchIssuesLoader; | |||
public SiblingsIssueMerger(SiblingsIssuesLoader siblingsIssuesLoader, SimpleTracker<DefaultIssue, SiblingIssue> tracker, IssueLifecycle issueLifecycle) { | |||
this.siblingsIssuesLoader = siblingsIssuesLoader; | |||
this.tracker = tracker; | |||
this.issueLifecycle = issueLifecycle; | |||
} | |||
/** | |||
* Look for all resolved/confirmed issues in short living branches targeting the current long living branch, and run | |||
* Look for all unclosed issues in branches/PR targeting the same long living branch, and run | |||
* a light issue tracking to find matches. Then merge issue attributes in the new issues. | |||
*/ | |||
public void tryMerge(Component component, Collection<DefaultIssue> newIssues) { | |||
Collection<ShortBranchIssue> shortBranchIssues = shortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component); | |||
Tracking<DefaultIssue, ShortBranchIssue> tracking = tracker.track(newIssues, shortBranchIssues); | |||
Collection<SiblingIssue> siblingIssues = siblingsIssuesLoader.loadCandidateSiblingIssuesForMerging(component); | |||
Tracking<DefaultIssue, SiblingIssue> tracking = tracker.track(newIssues, siblingIssues); | |||
Map<DefaultIssue, ShortBranchIssue> matchedRaws = tracking.getMatchedRaws(); | |||
Map<DefaultIssue, SiblingIssue> matchedRaws = tracking.getMatchedRaws(); | |||
Map<ShortBranchIssue, DefaultIssue> defaultIssues = shortBranchIssuesLoader.loadDefaultIssuesWithChanges(matchedRaws.values()); | |||
Map<SiblingIssue, DefaultIssue> defaultIssues = siblingsIssuesLoader.loadDefaultIssuesWithChanges(matchedRaws.values()); | |||
for (Map.Entry<DefaultIssue, ShortBranchIssue> e : matchedRaws.entrySet()) { | |||
ShortBranchIssue issue = e.getValue(); | |||
for (Map.Entry<DefaultIssue, SiblingIssue> e : matchedRaws.entrySet()) { | |||
SiblingIssue issue = e.getValue(); | |||
issueLifecycle.mergeConfirmedOrResolvedFromShortLivingBranch(e.getKey(), defaultIssues.get(issue), issue.getBranchName()); | |||
} | |||
} |
@@ -26,7 +26,7 @@ import java.util.Map; | |||
import java.util.Set; | |||
import java.util.stream.Collectors; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
import org.sonar.ce.task.projectanalysis.component.ShortBranchComponentsWithIssues; | |||
import org.sonar.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
@@ -38,22 +38,22 @@ import static org.sonar.api.utils.DateUtils.longToDate; | |||
import static org.sonar.core.util.stream.MoreCollectors.toList; | |||
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; | |||
public class ShortBranchIssuesLoader { | |||
public class SiblingsIssuesLoader { | |||
private final ShortBranchComponentsWithIssues shortBranchComponentsWithIssues; | |||
private final SiblingComponentsWithOpenIssues siblingComponentsWithOpenIssues; | |||
private final DbClient dbClient; | |||
private final ComponentIssuesLoader componentIssuesLoader; | |||
public ShortBranchIssuesLoader(ShortBranchComponentsWithIssues shortBranchComponentsWithIssues, DbClient dbClient, | |||
ComponentIssuesLoader componentIssuesLoader) { | |||
this.shortBranchComponentsWithIssues = shortBranchComponentsWithIssues; | |||
public SiblingsIssuesLoader(SiblingComponentsWithOpenIssues siblingComponentsWithOpenIssues, DbClient dbClient, | |||
ComponentIssuesLoader componentIssuesLoader) { | |||
this.siblingComponentsWithOpenIssues = siblingComponentsWithOpenIssues; | |||
this.dbClient = dbClient; | |||
this.componentIssuesLoader = componentIssuesLoader; | |||
} | |||
public Collection<ShortBranchIssue> loadCandidateIssuesForMergingInTargetBranch(Component component) { | |||
public Collection<SiblingIssue> loadCandidateSiblingIssuesForMerging(Component component) { | |||
String componentKey = ComponentDto.removeBranchAndPullRequestFromKey(component.getDbKey()); | |||
Set<String> uuids = shortBranchComponentsWithIssues.getUuids(componentKey); | |||
Set<String> uuids = siblingComponentsWithOpenIssues.getUuids(componentKey); | |||
if (uuids.isEmpty()) { | |||
return Collections.emptyList(); | |||
} | |||
@@ -61,22 +61,22 @@ public class ShortBranchIssuesLoader { | |||
try (DbSession session = dbClient.openSession(false)) { | |||
return dbClient.issueDao().selectOpenByComponentUuids(session, uuids) | |||
.stream() | |||
.map(ShortBranchIssuesLoader::toShortBranchIssue) | |||
.map(SiblingsIssuesLoader::toSiblingIssue) | |||
.collect(Collectors.toList()); | |||
} | |||
} | |||
private static ShortBranchIssue toShortBranchIssue(ShortBranchIssueDto dto) { | |||
return new ShortBranchIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getBranchName(), | |||
longToDate(dto.getIssueCreationDate())); | |||
private static SiblingIssue toSiblingIssue(ShortBranchIssueDto dto) { | |||
return new SiblingIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getBranchName(), | |||
longToDate(dto.getIssueUpdateDate())); | |||
} | |||
public Map<ShortBranchIssue, DefaultIssue> loadDefaultIssuesWithChanges(Collection<ShortBranchIssue> lightIssues) { | |||
public Map<SiblingIssue, DefaultIssue> loadDefaultIssuesWithChanges(Collection<SiblingIssue> lightIssues) { | |||
if (lightIssues.isEmpty()) { | |||
return Collections.emptyMap(); | |||
} | |||
Map<String, ShortBranchIssue> issuesByKey = lightIssues.stream().collect(Collectors.toMap(ShortBranchIssue::getKey, i -> i)); | |||
Map<String, SiblingIssue> issuesByKey = lightIssues.stream().collect(Collectors.toMap(SiblingIssue::getKey, i -> i)); | |||
try (DbSession session = dbClient.openSession(false)) { | |||
List<DefaultIssue> issues = dbClient.issueDao().selectByKeys(session, issuesByKey.keySet()) | |||
.stream() |
@@ -1,196 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.ce.task.projectanalysis.component; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.BranchType; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ComponentTesting; | |||
import org.sonar.db.issue.IssueTesting; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class ShortBranchComponentsWithIssuesTest { | |||
@Rule | |||
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
private ShortBranchComponentsWithIssues underTest; | |||
private ComponentDto long1; | |||
private ComponentDto fileWithNoIssues; | |||
private ComponentDto fileWithOneOpenIssue; | |||
private ComponentDto fileWithOneResolvedIssue; | |||
private ComponentDto fileWithOneOpenTwoResolvedIssues; | |||
private ComponentDto fileWithOneResolvedIssueInLong1Short1; | |||
private ComponentDto fileWithOneResolvedIssueInLong1Short2; | |||
private ComponentDto long2; | |||
private ComponentDto fileWithOneOpenIssueOnLong2; | |||
private ComponentDto fileWithOneResolvedIssueOnLong2; | |||
@Before | |||
public void setUp() { | |||
ComponentDto project = db.components().insertMainBranch(); | |||
long1 = db.components().insertProjectBranch(project, b -> b.setKey("long1"), b -> b.setBranchType(BranchType.LONG)); | |||
ComponentDto long1short1 = db.components().insertProjectBranch(project, | |||
b -> b.setKey("long1short1"), | |||
b -> b.setBranchType(BranchType.SHORT), | |||
b -> b.setMergeBranchUuid(long1.uuid())); | |||
ComponentDto long1short2 = db.components().insertProjectBranch(project, | |||
b -> b.setKey("long1short2"), | |||
b -> b.setBranchType(BranchType.SHORT), | |||
b -> b.setMergeBranchUuid(long1.uuid())); | |||
fileWithNoIssues = db.components().insertComponent(ComponentTesting.newFileDto(long1, null)); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
fileWithOneOpenIssue = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenIssue)); | |||
fileWithOneResolvedIssue = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneResolvedIssue).setStatus("RESOLVED")); | |||
fileWithOneOpenTwoResolvedIssues = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenTwoResolvedIssues)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenTwoResolvedIssues).setStatus("RESOLVED")); | |||
String fileKey = "file-x"; | |||
fileWithOneResolvedIssueInLong1Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null) | |||
.setDbKey(fileKey + ":BRANCH:long1short1")); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneResolvedIssueInLong1Short1).setStatus("RESOLVED")); | |||
fileWithOneResolvedIssueInLong1Short2 = db.components().insertComponent(ComponentTesting.newFileDto(long1short2, null) | |||
.setDbKey(fileKey + ":BRANCH:long1short2")); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short2, fileWithOneResolvedIssueInLong1Short2).setStatus("RESOLVED")); | |||
long2 = db.components().insertProjectBranch(project, b -> b.setKey("long2"), b -> b.setBranchType(BranchType.LONG)); | |||
ComponentDto long2short1 = db.components().insertProjectBranch(project, | |||
b -> b.setKey("long2short1"), | |||
b -> b.setBranchType(BranchType.SHORT), | |||
b -> b.setMergeBranchUuid(long2.uuid())); | |||
fileWithOneOpenIssueOnLong2 = db.components().insertComponent(ComponentTesting.newFileDto(long2short1, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long2short1, fileWithOneOpenIssueOnLong2)); | |||
fileWithOneResolvedIssueOnLong2 = db.components().insertComponent(ComponentTesting.newFileDto(long2short1, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long2short1, fileWithOneResolvedIssueOnLong2).setStatus("RESOLVED")); | |||
setRootUuid(long1.uuid()); | |||
underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient()); | |||
} | |||
@Test | |||
public void should_find_components_with_issues_to_merge_on_long1() { | |||
setRootUuid(long1.uuid()); | |||
assertThat(underTest.getUuids(fileWithNoIssues.getKey())).isEmpty(); | |||
assertThat(underTest.getUuids(fileWithOneOpenIssue.getKey())).containsOnly(fileWithOneOpenIssue.uuid()); | |||
assertThat(underTest.getUuids(fileWithOneResolvedIssue.getKey())).containsOnly(fileWithOneResolvedIssue.uuid()); | |||
assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssues.getKey())).containsOnly(fileWithOneOpenTwoResolvedIssues.uuid()); | |||
assertThat(fileWithOneResolvedIssueInLong1Short1.getKey()).isEqualTo(fileWithOneResolvedIssueInLong1Short2.getKey()); | |||
assertThat(underTest.getUuids(fileWithOneResolvedIssueInLong1Short1.getKey())).containsOnly( | |||
fileWithOneResolvedIssueInLong1Short1.uuid(), | |||
fileWithOneResolvedIssueInLong1Short2.uuid()); | |||
} | |||
@Test | |||
public void should_find_components_with_issues_to_merge_on_long2() { | |||
setRootUuid(long2.uuid()); | |||
underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient()); | |||
assertThat(underTest.getUuids(fileWithOneResolvedIssue.getKey())).isEmpty(); | |||
assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong2.getKey())).containsOnly(fileWithOneResolvedIssueOnLong2.uuid()); | |||
assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong2.getKey())).containsOnly(fileWithOneOpenIssueOnLong2.uuid()); | |||
} | |||
@Test | |||
public void should_find_components_with_issues_to_merge_on_derived_short() { | |||
ComponentDto project = db.components().insertMainBranch(); | |||
setRootUuid(project.uuid()); | |||
ComponentDto branch = db.components().insertProjectBranch(project, | |||
b -> b.setBranchType(BranchType.SHORT), | |||
b -> b.setMergeBranchUuid(project.uuid())); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
ComponentDto fileWithResolvedIssue = db.components().insertComponent(ComponentTesting.newFileDto(branch, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch, fileWithResolvedIssue).setStatus("RESOLVED")); | |||
underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient()); | |||
assertThat(underTest.getUuids(fileWithResolvedIssue.getKey())).hasSize(1); | |||
} | |||
@Test | |||
public void should_find_components_with_issues_to_merge_on_derived_pull_request() { | |||
ComponentDto project = db.components().insertMainBranch(); | |||
setRootUuid(project.uuid()); | |||
ComponentDto pullRequest = db.components().insertProjectBranch(project, | |||
b -> b.setBranchType(BranchType.PULL_REQUEST), | |||
b -> b.setMergeBranchUuid(project.uuid())); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
ComponentDto fileWithResolvedIssue = db.components().insertComponent(ComponentTesting.newFileDto(pullRequest, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, pullRequest, fileWithResolvedIssue).setStatus("RESOLVED")); | |||
underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient()); | |||
assertThat(underTest.getUuids(fileWithResolvedIssue.getKey())).hasSize(1); | |||
} | |||
@Test | |||
public void should_not_find_components_with_issues_to_merge_on_derived_long() { | |||
ComponentDto project = db.components().insertMainBranch(); | |||
setRootUuid(project.uuid()); | |||
ComponentDto branch = db.components().insertProjectBranch(project, | |||
b -> b.setBranchType(BranchType.LONG), | |||
b -> b.setMergeBranchUuid(project.uuid())); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
ComponentDto fileWithResolvedIssue = db.components().insertComponent(ComponentTesting.newFileDto(branch, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch, fileWithResolvedIssue).setStatus("RESOLVED")); | |||
underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient()); | |||
assertThat(underTest.getUuids(fileWithResolvedIssue.getKey())).isEmpty(); | |||
} | |||
private void setRootUuid(String uuid) { | |||
Component root = mock(Component.class); | |||
when(root.getUuid()).thenReturn(uuid); | |||
treeRootHolder.setRoot(root); | |||
} | |||
} |
@@ -0,0 +1,229 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.ce.task.projectanalysis.component; | |||
import java.util.Optional; | |||
import javax.annotation.Nullable; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; | |||
import org.sonar.ce.task.projectanalysis.analysis.Branch; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.BranchType; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ComponentTesting; | |||
import org.sonar.db.issue.IssueTesting; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class SiblingComponentsWithOpenIssuesTest { | |||
@Rule | |||
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); | |||
@Rule | |||
public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule(); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
private SiblingComponentsWithOpenIssues underTest; | |||
private ComponentDto long1; | |||
private ComponentDto fileWithNoIssuesOnLong1; | |||
private ComponentDto fileWithOneOpenIssueOnLong1Short1; | |||
private ComponentDto fileWithOneResolvedIssueOnLong1Short1; | |||
private ComponentDto fileWithOneOpenTwoResolvedIssuesOnLong1Short1; | |||
private ComponentDto fileXWithOneResolvedIssueOnLong1Short1; | |||
private ComponentDto fileXWithOneResolvedIssueOnLong1Short2; | |||
private ComponentDto long2; | |||
private ComponentDto fileWithOneOpenIssueOnLong2Short1; | |||
private ComponentDto fileWithOneResolvedIssueOnLong2Short1; | |||
private ComponentDto long1short1; | |||
@Before | |||
public void setUp() { | |||
ComponentDto project = db.components().insertMainBranch(); | |||
long1 = db.components().insertProjectBranch(project, b -> b.setKey("long1"), b -> b.setBranchType(BranchType.LONG)); | |||
long1short1 = db.components().insertProjectBranch(project, | |||
b -> b.setKey("long1short1"), | |||
b -> b.setBranchType(BranchType.SHORT), | |||
b -> b.setMergeBranchUuid(long1.uuid())); | |||
ComponentDto long1short2 = db.components().insertProjectBranch(project, | |||
b -> b.setKey("long1short2"), | |||
b -> b.setBranchType(BranchType.SHORT), | |||
b -> b.setMergeBranchUuid(long1.uuid())); | |||
fileWithNoIssuesOnLong1 = db.components().insertComponent(ComponentTesting.newFileDto(long1, null)); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
fileWithOneOpenIssueOnLong1Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenIssueOnLong1Short1)); | |||
fileWithOneResolvedIssueOnLong1Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneResolvedIssueOnLong1Short1).setStatus("RESOLVED")); | |||
fileWithOneOpenTwoResolvedIssuesOnLong1Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenTwoResolvedIssuesOnLong1Short1)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenTwoResolvedIssuesOnLong1Short1).setStatus("RESOLVED")); | |||
String fileKey = "file-x"; | |||
fileXWithOneResolvedIssueOnLong1Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null) | |||
.setDbKey(fileKey + ":BRANCH:long1short1")); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileXWithOneResolvedIssueOnLong1Short1).setStatus("RESOLVED")); | |||
fileXWithOneResolvedIssueOnLong1Short2 = db.components().insertComponent(ComponentTesting.newFileDto(long1short2, null) | |||
.setDbKey(fileKey + ":BRANCH:long1short2")); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long1short2, fileXWithOneResolvedIssueOnLong1Short2).setStatus("RESOLVED")); | |||
long2 = db.components().insertProjectBranch(project, b -> b.setKey("long2"), b -> b.setBranchType(BranchType.LONG)); | |||
ComponentDto long2short1 = db.components().insertProjectBranch(project, | |||
b -> b.setKey("long2short1"), | |||
b -> b.setBranchType(BranchType.SHORT), | |||
b -> b.setMergeBranchUuid(long2.uuid())); | |||
fileWithOneOpenIssueOnLong2Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long2short1, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long2short1, fileWithOneOpenIssueOnLong2Short1)); | |||
fileWithOneResolvedIssueOnLong2Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long2short1, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, long2short1, fileWithOneResolvedIssueOnLong2Short1).setStatus("RESOLVED")); | |||
setCurrentBranchUuid(long1.uuid()); | |||
underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient()); | |||
} | |||
@Test | |||
public void should_find_sibling_components_with_open_issues_for_long1() { | |||
setCurrentBranchUuid(long1.uuid()); | |||
setReferenceBranchUuid(null); | |||
assertThat(underTest.getUuids(fileWithNoIssuesOnLong1.getKey())).isEmpty(); | |||
assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong1Short1.getKey())).containsOnly(fileWithOneOpenIssueOnLong1Short1.uuid()); | |||
assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong1Short1.getKey())).containsOnly(fileWithOneResolvedIssueOnLong1Short1.uuid()); | |||
assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssuesOnLong1Short1.getKey())).containsOnly(fileWithOneOpenTwoResolvedIssuesOnLong1Short1.uuid()); | |||
assertThat(fileXWithOneResolvedIssueOnLong1Short1.getKey()).isEqualTo(fileXWithOneResolvedIssueOnLong1Short2.getKey()); | |||
assertThat(underTest.getUuids(fileXWithOneResolvedIssueOnLong1Short1.getKey())).containsOnly( | |||
fileXWithOneResolvedIssueOnLong1Short1.uuid(), | |||
fileXWithOneResolvedIssueOnLong1Short2.uuid()); | |||
} | |||
@Test | |||
public void should_find_sibling_components_with_open_issues_for_short1() { | |||
setCurrentBranchUuid(long1short1.uuid()); | |||
setReferenceBranchUuid(long1.uuid()); | |||
assertThat(underTest.getUuids(fileWithNoIssuesOnLong1.getKey())).isEmpty(); | |||
assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong1Short1.getKey())).isEmpty(); | |||
assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong1Short1.getKey())).isEmpty(); | |||
assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssuesOnLong1Short1.getKey())).isEmpty(); | |||
assertThat(underTest.getUuids(fileXWithOneResolvedIssueOnLong1Short1.getKey())).containsOnly( | |||
fileXWithOneResolvedIssueOnLong1Short2.uuid()); | |||
} | |||
@Test | |||
public void should_find_sibling_components_with_open_issues_for_long2() { | |||
setCurrentBranchUuid(long2.uuid()); | |||
setReferenceBranchUuid(null); | |||
underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient()); | |||
assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong1Short1.getKey())).isEmpty(); | |||
assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong2Short1.getKey())).containsOnly(fileWithOneResolvedIssueOnLong2Short1.uuid()); | |||
assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong2Short1.getKey())).containsOnly(fileWithOneOpenIssueOnLong2Short1.uuid()); | |||
} | |||
@Test | |||
public void should_find_sibling_components_with_open_issues_from_short() { | |||
ComponentDto project = db.components().insertMainBranch(); | |||
setCurrentBranchUuid(project.uuid()); | |||
setReferenceBranchUuid(null); | |||
ComponentDto branch = db.components().insertProjectBranch(project, | |||
b -> b.setBranchType(BranchType.SHORT), | |||
b -> b.setMergeBranchUuid(project.uuid())); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
ComponentDto fileWithResolvedIssueOnShort = db.components().insertComponent(ComponentTesting.newFileDto(branch, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch, fileWithResolvedIssueOnShort).setStatus("RESOLVED")); | |||
underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient()); | |||
assertThat(underTest.getUuids(fileWithResolvedIssueOnShort.getKey())).hasSize(1); | |||
} | |||
@Test | |||
public void should_find_sibling_components_with_open_issues_from_pullrequest() { | |||
ComponentDto project = db.components().insertMainBranch(); | |||
setCurrentBranchUuid(project.uuid()); | |||
setReferenceBranchUuid(null); | |||
ComponentDto pullRequest = db.components().insertProjectBranch(project, | |||
b -> b.setBranchType(BranchType.PULL_REQUEST), | |||
b -> b.setMergeBranchUuid(project.uuid())); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
ComponentDto fileWithResolvedIssueOnPullrequest = db.components().insertComponent(ComponentTesting.newFileDto(pullRequest, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, pullRequest, fileWithResolvedIssueOnPullrequest).setStatus("RESOLVED")); | |||
underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient()); | |||
assertThat(underTest.getUuids(fileWithResolvedIssueOnPullrequest.getKey())).hasSize(1); | |||
} | |||
@Test | |||
public void should_not_find_sibling_components_on_derived_long() { | |||
ComponentDto project = db.components().insertMainBranch(); | |||
setCurrentBranchUuid(project.uuid()); | |||
setReferenceBranchUuid(null); | |||
ComponentDto derivedLongBranch = db.components().insertProjectBranch(project, | |||
b -> b.setBranchType(BranchType.LONG), | |||
b -> b.setMergeBranchUuid(project.uuid())); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
ComponentDto fileWithResolvedIssueOnDerivedLongBranch = db.components().insertComponent(ComponentTesting.newFileDto(derivedLongBranch, null)); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, derivedLongBranch, fileWithResolvedIssueOnDerivedLongBranch).setStatus("RESOLVED")); | |||
underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient()); | |||
assertThat(underTest.getUuids(fileWithResolvedIssueOnDerivedLongBranch.getKey())).isEmpty(); | |||
} | |||
private void setCurrentBranchUuid(String uuid) { | |||
Component root = mock(Component.class); | |||
when(root.getUuid()).thenReturn(uuid); | |||
treeRootHolder.setRoot(root); | |||
} | |||
private void setReferenceBranchUuid(@Nullable String uuid) { | |||
Branch branch = mock(Branch.class); | |||
when(branch.getMergeBranchUuid()).thenReturn(Optional.ofNullable(uuid)); | |||
metadataHolder.setBranch(branch); | |||
} | |||
} |
@@ -117,7 +117,7 @@ public class IntegrateIssuesVisitorTest { | |||
private IssueLifecycle issueLifecycle = mock(IssueLifecycle.class); | |||
private IssueVisitor issueVisitor = mock(IssueVisitor.class); | |||
private MergeBranchComponentUuids mergeBranchComponentsUuids = mock(MergeBranchComponentUuids.class); | |||
private ShortBranchIssueMerger issueStatusCopier = mock(ShortBranchIssueMerger.class); | |||
private SiblingsIssueMerger issueStatusCopier = mock(SiblingsIssueMerger.class); | |||
private MergeBranchComponentUuids mergeBranchComponentUuids = mock(MergeBranchComponentUuids.class); | |||
private SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class); | |||
private NewLinesRepository newLinesRepository = mock(NewLinesRepository.class); |
@@ -23,6 +23,7 @@ import java.time.Instant; | |||
import java.time.temporal.ChronoUnit; | |||
import java.util.Collections; | |||
import java.util.Date; | |||
import java.util.Optional; | |||
import javax.annotation.Nullable; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
@@ -34,7 +35,9 @@ import org.sonar.api.config.internal.MapSettings; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.ce.task.projectanalysis.component.ShortBranchComponentsWithIssues; | |||
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; | |||
import org.sonar.ce.task.projectanalysis.analysis.Branch; | |||
import org.sonar.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues; | |||
import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.FieldDiffs; | |||
@@ -52,13 +55,17 @@ import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.ArgumentMatchers.eq; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyZeroInteractions; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; | |||
import static org.sonar.db.component.ComponentTesting.newFileDto; | |||
public class ShortBranchIssueMergerTest { | |||
public class SiblingsIssueMergerTest { | |||
@Mock | |||
private IssueLifecycle issueLifecycle; | |||
@Mock | |||
private Branch branch; | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
@@ -68,6 +75,9 @@ public class ShortBranchIssueMergerTest { | |||
.addChildren(FILE_1) | |||
.build()); | |||
@Rule | |||
public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule(); | |||
private static final String PROJECT_KEY = "project"; | |||
private static final int PROJECT_REF = 1; | |||
private static final String PROJECT_UUID = "projectUuid"; | |||
@@ -81,8 +91,8 @@ public class ShortBranchIssueMergerTest { | |||
.setUuid(FILE_1_UUID) | |||
.build(); | |||
private SimpleTracker<DefaultIssue, ShortBranchIssue> tracker = new SimpleTracker<>(); | |||
private ShortBranchIssueMerger copier; | |||
private SimpleTracker<DefaultIssue, SiblingIssue> tracker = new SimpleTracker<>(); | |||
private SiblingsIssueMerger copier; | |||
private ComponentDto fileOnBranch1Dto; | |||
private ComponentDto fileOnBranch2Dto; | |||
private ComponentDto fileOnBranch3Dto; | |||
@@ -95,9 +105,11 @@ public class ShortBranchIssueMergerTest { | |||
@Before | |||
public void setUp() { | |||
MockitoAnnotations.initMocks(this); | |||
when(branch.getMergeBranchUuid()).thenReturn(Optional.empty()); | |||
metadataHolder.setBranch(branch); | |||
DbClient dbClient = db.getDbClient(); | |||
ComponentIssuesLoader componentIssuesLoader = new ComponentIssuesLoader(dbClient, null, null, new MapSettings().asConfig(), System2.INSTANCE); | |||
copier = new ShortBranchIssueMerger(new ShortBranchIssuesLoader(new ShortBranchComponentsWithIssues(treeRootHolder, dbClient), dbClient, componentIssuesLoader), tracker, | |||
copier = new SiblingsIssueMerger(new SiblingsIssuesLoader(new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, dbClient), dbClient, componentIssuesLoader), tracker, | |||
issueLifecycle); | |||
projectDto = db.components().insertMainBranch(p -> p.setDbKey(PROJECT_KEY).setUuid(PROJECT_UUID)); | |||
branch1Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch1") | |||
@@ -145,53 +157,22 @@ public class ShortBranchIssueMergerTest { | |||
} | |||
@Test | |||
public void prefer_resolved_issues() { | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum")); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum")); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setLine(1).setChecksum("checksum")); | |||
DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date()); | |||
copier.tryMerge(FILE_1, Collections.singleton(newIssue)); | |||
ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); | |||
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch3")); | |||
assertThat(issueToMerge.getValue().key()).isEqualTo("issue3"); | |||
} | |||
@Test | |||
public void prefer_confirmed_issues_if_no_resolved() { | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum")); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum")); | |||
DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date()); | |||
copier.tryMerge(FILE_1, Collections.singleton(newIssue)); | |||
ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); | |||
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch3")); | |||
assertThat(issueToMerge.getValue().key()).isEqualTo("issue3"); | |||
} | |||
@Test | |||
public void prefer_older_issues() { | |||
public void prefer_more_recently_updated_issues() { | |||
Instant now = Instant.now(); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum") | |||
.setIssueCreationDate(Date.from(now.plus(2, ChronoUnit.SECONDS)))); | |||
.setIssueUpdateDate(Date.from(now.plus(2, ChronoUnit.SECONDS)))); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum") | |||
.setIssueCreationDate(Date.from(now.plus(1, ChronoUnit.SECONDS)))); | |||
.setIssueUpdateDate(Date.from(now.plus(1, ChronoUnit.SECONDS)))); | |||
db.issues().insertIssue(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum") | |||
.setIssueCreationDate(Date.from(now))); | |||
.setIssueUpdateDate(Date.from(now))); | |||
DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date()); | |||
copier.tryMerge(FILE_1, Collections.singleton(newIssue)); | |||
ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); | |||
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch3")); | |||
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch1")); | |||
assertThat(issueToMerge.getValue().key()).isEqualTo("issue3"); | |||
assertThat(issueToMerge.getValue().key()).isEqualTo("issue1"); | |||
} | |||
@Test |
@@ -364,8 +364,8 @@ public class ComponentDao implements Dao { | |||
return mapper(dbSession).selectProjectsByNameQuery(nameQueryForSql, includeModules); | |||
} | |||
public List<KeyWithUuidDto> selectComponentKeysHavingIssuesToMerge(DbSession dbSession, String mergeBranchUuid) { | |||
return mapper(dbSession).selectComponentKeysHavingIssuesToMerge(mergeBranchUuid); | |||
public List<KeyWithUuidDto> selectAllSiblingComponentKeysHavingOpenIssues(DbSession dbSession, String referenceBranchUuid, String currentBranchUuid) { | |||
return mapper(dbSession).selectAllSiblingComponentKeysHavingOpenIssues(referenceBranchUuid, currentBranchUuid); | |||
} | |||
/** |
@@ -166,7 +166,8 @@ public interface ComponentMapper { | |||
void updateTags(ComponentDto component); | |||
List<KeyWithUuidDto> selectComponentKeysHavingIssuesToMerge(@Param("mergeBranchUuid") String mergeBranchUuid); | |||
List<KeyWithUuidDto> selectAllSiblingComponentKeysHavingOpenIssues(@Param("referenceBranchUuid") String referenceBranchUuid, | |||
@Param("currentBranchUuid") String currentBranchUuid); | |||
List<ProjectNclocDistributionDto> selectPrivateProjectsWithNcloc(@Param("organizationUuid") String organizationUuid); | |||
@@ -33,7 +33,7 @@ public final class ShortBranchIssueDto implements Serializable { | |||
private Integer line; | |||
private String checksum; | |||
private String status; | |||
private Long issueCreationDate; | |||
private Long issueUpdateDate; | |||
// joins | |||
private String ruleKey; | |||
@@ -109,12 +109,12 @@ public final class ShortBranchIssueDto implements Serializable { | |||
return RuleKey.of(ruleRepo, ruleKey); | |||
} | |||
public Long getIssueCreationDate() { | |||
return issueCreationDate; | |||
public Long getIssueUpdateDate() { | |||
return issueUpdateDate; | |||
} | |||
public ShortBranchIssueDto setIssueCreationDate(Long issueCreationDate) { | |||
this.issueCreationDate = issueCreationDate; | |||
public ShortBranchIssueDto setIssueUpdateDate(Long issueUpdateDate) { | |||
this.issueUpdateDate = issueUpdateDate; | |||
return this; | |||
} | |||
@@ -816,14 +816,15 @@ | |||
DELETE FROM projects WHERE id=#{id,jdbcType=BIGINT} | |||
</delete> | |||
<select id="selectComponentKeysHavingIssuesToMerge" resultType="KeyWithUuid"> | |||
<select id="selectAllSiblingComponentKeysHavingOpenIssues" resultType="KeyWithUuid"> | |||
SELECT DISTINCT p.kee as kee, p.uuid as uuid FROM projects p | |||
JOIN issues i | |||
ON p.uuid = i.component_uuid | |||
JOIN project_branches b | |||
ON i.project_uuid = b.uuid | |||
AND (b.branch_type = 'SHORT' OR b.branch_type = 'PULL_REQUEST') | |||
AND b.merge_branch_uuid = #{mergeBranchUuid,jdbcType=VARCHAR} | |||
AND b.merge_branch_uuid = #{referenceBranchUuid,jdbcType=VARCHAR} | |||
AND b.uuid != #{currentBranchUuid,jdbcType=VARCHAR} | |||
AND i.status != 'CLOSED' | |||
</select> | |||
@@ -290,7 +290,7 @@ | |||
i.line as line, | |||
i.status as status, | |||
i.checksum as checksum, | |||
i.issue_creation_date as issueCreationDate, | |||
i.issue_update_date as issueUpdateDate, | |||
r.plugin_rule_key as ruleKey, | |||
r.plugin_name as ruleRepo, | |||
b.kee as branchName |
@@ -235,7 +235,7 @@ public class IssueDaoTest { | |||
assertThat(fp.getRuleKey()).isNotNull(); | |||
assertThat(fp.getStatus()).isNotNull(); | |||
assertThat(fp.getBranchName()).isEqualTo("feature/foo"); | |||
assertThat(fp.getIssueCreationDate()).isNotNull(); | |||
assertThat(fp.getIssueUpdateDate()).isNotNull(); | |||
} | |||
@Test |
@@ -647,7 +647,7 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure. | |||
} | |||
@Override | |||
public Date getCreationDate() { | |||
return creationDate; | |||
public Date getUpdateDate() { | |||
return updateDate; | |||
} | |||
} |
@@ -46,8 +46,8 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> { | |||
SearchKey rawKey = searchKeyFactory.apply(raw); | |||
Collection<BASE> bases = baseSearch.get(rawKey); | |||
bases.stream() | |||
.sorted(comparing(this::statusRank).reversed() | |||
.thenComparing(comparing(Trackable::getCreationDate))) | |||
// Choose the more recently updated issue first to get the latest changes in siblings | |||
.sorted(comparing(Trackable::getUpdateDate).reversed()) | |||
.findFirst() | |||
.ifPresent(match -> { | |||
tracking.match(raw, match); |
@@ -46,7 +46,7 @@ public interface Trackable { | |||
String getStatus(); | |||
/** | |||
* Functional creation date for the issue. See {@link DefaultIssue#creationDate()} | |||
* Functional update date for the issue. See {@link DefaultIssue#updateDate()} | |||
*/ | |||
Date getCreationDate(); | |||
Date getUpdateDate(); | |||
} |
@@ -454,14 +454,14 @@ public class TrackerTest { | |||
private final Integer line; | |||
private final String message, lineHash; | |||
private final String status; | |||
private final Date creationDate; | |||
private final Date updateDate; | |||
Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status, Date creationDate) { | |||
Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status, Date updateDate) { | |||
this.line = line; | |||
this.lineHash = lineHash; | |||
this.ruleKey = ruleKey; | |||
this.status = status; | |||
this.creationDate = creationDate; | |||
this.updateDate = updateDate; | |||
this.message = trim(message); | |||
} | |||
@@ -491,8 +491,8 @@ public class TrackerTest { | |||
} | |||
@Override | |||
public Date getCreationDate() { | |||
return creationDate; | |||
public Date getUpdateDate() { | |||
return updateDate; | |||
} | |||
} | |||