private final String lineHash;
private final RuleKey ruleKey;
private final String status;
+ private final String branchName;
- public ShortBranchIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status) {
+ public ShortBranchIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName) {
this.key = key;
this.line = line;
this.message = message;
this.lineHash = lineHash;
this.ruleKey = ruleKey;
this.status = status;
+ this.branchName = branchName;
}
public String getKey() {
return status;
}
+ public String getBranchName() {
+ return branchName;
+ }
+
@Override
public int hashCode() {
return key.hashCode();
// joins
private String ruleKey;
private String ruleRepo;
+ private String branchName;
public String getKey() {
return kee;
return this;
}
+ public String getBranchName() {
+ return branchName;
+ }
+
+ public ShortBranchIssueDto setBranchName(String s) {
+ this.branchName = s;
+ return this;
+ }
+
public String getStatus() {
return status;
}
}
public static ShortBranchIssue toShortBranchIssue(ShortBranchIssueDto dto) {
- return new ShortBranchIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus());
+ return new ShortBranchIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getBranchName());
}
}
i.status as status,
i.checksum as checksum,
r.plugin_rule_key as ruleKey,
- r.plugin_name as ruleRepo
+ r.plugin_name as ruleRepo,
+ b.kee as branchName
from issues i
inner join rules r on r.id = i.rule_id
+ inner join project_branches b on i.project_uuid = b.uuid
where i.component_uuid in
<foreach collection="list" open="(" close=")" item="key" separator=",">
#{key,jdbcType=VARCHAR}
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.db.RowNotFoundException;
+import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.organization.OrganizationDto;
@Test
public void selectResolvedOrConfirmedByComponentUuid() {
RuleDefinitionDto rule = db.rules().insert();
- ComponentDto project = db.components().insertPrivateProject();
- ComponentDto file = db.components().insertComponent(newFileDto(project));
- IssueDto openIssue = db.issues().insert(rule, project, file, i -> i.setStatus(Issue.STATUS_OPEN).setResolution(null));
- IssueDto closedIssue = db.issues().insert(rule, project, file, i -> i.setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED));
- IssueDto reopenedIssue = db.issues().insert(rule, project, file, i -> i.setStatus(Issue.STATUS_REOPENED).setResolution(null));
- IssueDto confirmedIssue = db.issues().insert(rule, project, file, i -> i.setStatus(Issue.STATUS_CONFIRMED).setResolution(null));
- IssueDto wontfixIssue = db.issues().insert(rule, project, file, i -> i.setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX));
- IssueDto fpIssue = db.issues().insert(rule, project, file, i -> i.setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FALSE_POSITIVE));
+ ComponentDto project = db.components().insertMainBranch();
+ ComponentDto projectBranch = db.components().insertProjectBranch(project,
+ b -> b.setKey("feature/foo")
+ .setBranchType(BranchType.SHORT));
+
+ ComponentDto file = db.components().insertComponent(newFileDto(projectBranch));
+
+ IssueDto openIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_OPEN).setResolution(null));
+ IssueDto closedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED));
+ IssueDto reopenedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_REOPENED).setResolution(null));
+ IssueDto confirmedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_CONFIRMED).setResolution(null));
+ IssueDto wontfixIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX));
+ IssueDto fpIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FALSE_POSITIVE));
assertThat(underTest.selectResolvedOrConfirmedByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())))
.extracting("kee")
@Test
public void selectResolvedOrConfirmedByComponentUuid_should_correctly_map_required_fields() {
RuleDefinitionDto rule = db.rules().insert();
- ComponentDto project = db.components().insertPrivateProject();
- ComponentDto file = db.components().insertComponent(newFileDto(project));
- IssueDto fpIssue = db.issues().insert(rule, project, file, i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE"));
+ ComponentDto project = db.components().insertMainBranch();
+ ComponentDto projectBranch = db.components().insertProjectBranch(project,
+ b -> b.setKey("feature/foo")
+ .setBranchType(BranchType.SHORT));
+
+ ComponentDto file = db.components().insertComponent(newFileDto(projectBranch));
+ IssueDto fpIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE"));
ShortBranchIssueDto fp = underTest.selectResolvedOrConfirmedByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())).get(0);
assertThat(fp.getLine()).isEqualTo(fpIssue.getLine());
private final AnalysisMetadataHolder analysisMetadataHolder;
private final DbClient dbClient;
private Map<String, String> uuidsByKey;
+ private String mergeBranchName;
public MergeBranchComponentUuids(AnalysisMetadataHolder analysisMetadataHolder, DbClient dbClient) {
this.analysisMetadataHolder = analysisMetadataHolder;
this.dbClient = dbClient;
}
- private void loadMergeBranchComponents() {
- String mergeBranchUuid = analysisMetadataHolder.getBranch().get().getMergeBranchUuid().get();
+ private void lazyInit() {
+ if (uuidsByKey == null) {
+ String mergeBranchUuid = analysisMetadataHolder.getBranch().get().getMergeBranchUuid().get();
+
+ uuidsByKey = new HashMap<>();
+ try (DbSession dbSession = dbClient.openSession(false)) {
- uuidsByKey = new HashMap<>();
- try (DbSession dbSession = dbClient.openSession(false)) {
+ List<ComponentDto> components = dbClient.componentDao().selectByProjectUuid(mergeBranchUuid, dbSession);
+ for (ComponentDto dto : components) {
+ uuidsByKey.put(dto.getKey(), dto.uuid());
+ }
- List<ComponentDto> components = dbClient.componentDao().selectByProjectUuid(mergeBranchUuid, dbSession);
- for (ComponentDto dto : components) {
- uuidsByKey.put(dto.getKey(), dto.uuid());
+ mergeBranchName = dbClient.branchDao().selectByUuid(dbSession, mergeBranchUuid).get().getKey();
}
}
}
+ public String getMergeBranchName() {
+ lazyInit();
+ return mergeBranchName;
+ }
+
@CheckForNull
public String getUuid(String dbKey) {
- if (uuidsByKey == null) {
- loadMergeBranchComponents();
- }
-
+ lazyInit();
String cleanComponentKey = removeBranchFromKey(dbKey);
return uuidsByKey.get(cleanComponentKey);
}
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
+import org.sonar.server.computation.task.projectanalysis.component.MergeBranchComponentUuids;
import org.sonar.server.computation.task.projectanalysis.component.TypeAwareVisitorAdapter;
import org.sonar.server.util.cache.DiskCache;
private final IssueTrackingDelegator issueTracking;
private final ShortBranchIssueMerger 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, ShortBranchIssueMerger issueStatusCopier,
+ MergeBranchComponentUuids mergeBranchComponentUuids) {
super(CrawlerDepthLimit.FILE, POST_ORDER);
this.issueCache = issueCache;
this.issueLifecycle = issueLifecycle;
this.analysisMetadataHolder = analysisMetadataHolder;
this.issueTracking = issueTracking;
this.issueStatusCopier = issueStatusCopier;
+ this.mergeBranchComponentUuids = mergeBranchComponentUuids;
}
@Override
for (Map.Entry<DefaultIssue, DefaultIssue> entry : matched.entrySet()) {
DefaultIssue raw = entry.getKey();
DefaultIssue base = entry.getValue();
- issueLifecycle.copyExistingOpenIssueFromLongLivingBranch(raw, base);
+ issueLifecycle.copyExistingOpenIssueFromLongLivingBranch(raw, base, mergeBranchComponentUuids.getMergeBranchName());
process(component, raw, cacheAppender);
}
}
private final IssueChangeContext changeContext;
private final IssueFieldsSetter updater;
private final DebtCalculator debtCalculator;
+ private final AnalysisMetadataHolder analysisMetadataHolder;
public IssueLifecycle(AnalysisMetadataHolder analysisMetadataHolder, IssueWorkflow workflow, IssueFieldsSetter updater, DebtCalculator debtCalculator) {
- this(IssueChangeContext.createScan(new Date(analysisMetadataHolder.getAnalysisDate())), workflow, updater, debtCalculator);
+ this(analysisMetadataHolder, IssueChangeContext.createScan(new Date(analysisMetadataHolder.getAnalysisDate())), workflow, updater, debtCalculator);
}
@VisibleForTesting
- IssueLifecycle(IssueChangeContext changeContext, IssueWorkflow workflow, IssueFieldsSetter updater, DebtCalculator debtCalculator) {
+ IssueLifecycle(AnalysisMetadataHolder analysisMetadataHolder, IssueChangeContext changeContext, IssueWorkflow workflow, IssueFieldsSetter updater,
+ DebtCalculator debtCalculator) {
+ this.analysisMetadataHolder = analysisMetadataHolder;
this.workflow = workflow;
this.updater = updater;
this.debtCalculator = debtCalculator;
issue.setEffort(debtCalculator.calculate(issue));
}
- public void copyExistingOpenIssueFromLongLivingBranch(DefaultIssue raw, DefaultIssue base) {
+ public void copyExistingOpenIssueFromLongLivingBranch(DefaultIssue raw, DefaultIssue base, String fromLongBranchName) {
raw.setKey(Uuids.create());
raw.setNew(false);
copyIssueAttributes(raw, base);
+ raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_LONG_BRANCH, fromLongBranchName, analysisMetadataHolder.getBranch().get().getName());
}
- public void copyIssueAttributes(DefaultIssue to, DefaultIssue from) {
+ public void mergeConfirmedOrResolvedFromShortLivingBranch(DefaultIssue raw, DefaultIssue base, String fromShortBranchName) {
+ copyIssueAttributes(raw, base);
+ raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_SHORT_BRANCH, fromShortBranchName, analysisMetadataHolder.getBranch().get().getName());
+ }
+
+ private void copyIssueAttributes(DefaultIssue to, DefaultIssue from) {
to.setCopied(true);
copyFields(to, from);
if (from.manualSeverity()) {
for (Map.Entry<DefaultIssue, ShortBranchIssue> e : matchedRaws.entrySet()) {
ShortBranchIssue issue = e.getValue();
- issueLifecycle.copyIssueAttributes(e.getKey(), defaultIssues.get(issue));
+ issueLifecycle.mergeConfirmedOrResolvedFromShortLivingBranch(e.getKey(), defaultIssues.get(issue), issue.getBranchName());
}
}
}
public static final String STATUS = "status";
public static final String AUTHOR = "author";
public static final String FILE = "file";
+ public static final String FROM_LONG_BRANCH = "from_long_branch";
+ public static final String FROM_SHORT_BRANCH = "from_short_branch";
/**
* It should be renamed to 'effort', but it hasn't been done to prevent a massive update in database
ComponentDto file = files.get(fileUuid);
return file == null ? null : file.longName();
}
+
}
}
private MergeBranchComponentUuids mergeBranchComponentsUuids;
@Mock
private ShortBranchIssueMerger issueStatusCopier;
+ @Mock
+ private MergeBranchComponentUuids mergeBranchComponentUuids;
ArgumentCaptor<DefaultIssue> defaultIssueCaptor;
treeRootHolder.setRoot(PROJECT);
issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true);
- underTest = new IntegrateIssuesVisitor(issueCache, issueLifecycle, issueVisitors, analysisMetadataHolder, trackingDelegator, issueStatusCopier);
+ underTest = new IntegrateIssuesVisitor(issueCache, issueLifecycle, issueVisitors, analysisMetadataHolder, trackingDelegator, issueStatusCopier, mergeBranchComponentUuids);
}
@Test
public void copy_issues_when_creating_new_long_living_branch() throws Exception {
when(mergeBranchComponentsUuids.getUuid(FILE_KEY)).thenReturn(FILE_UUID_ON_BRANCH);
+ when(mergeBranchComponentUuids.getMergeBranchName()).thenReturn("master");
when(analysisMetadataHolder.isLongLivingBranch()).thenReturn(true);
when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true);
ArgumentCaptor<DefaultIssue> rawIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
ArgumentCaptor<DefaultIssue> baseIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueLifecycle).copyExistingOpenIssueFromLongLivingBranch(rawIssueCaptor.capture(), baseIssueCaptor.capture());
+ verify(issueLifecycle).copyExistingOpenIssueFromLongLivingBranch(rawIssueCaptor.capture(), baseIssueCaptor.capture(), eq("master"));
assertThat(rawIssueCaptor.getValue().severity()).isEqualTo(Severity.BLOCKER);
assertThat(baseIssueCaptor.getValue().severity()).isEqualTo(Severity.MAJOR);
import com.google.common.collect.ImmutableMap;
import java.util.Date;
+import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.utils.Duration;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.db.protobuf.DbCommons;
import org.sonar.db.protobuf.DbIssues;
+import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.workflow.IssueWorkflow;
DebtCalculator debtCalculator = mock(DebtCalculator.class);
- IssueLifecycle underTest = new IssueLifecycle(issueChangeContext, workflow, updater, debtCalculator);
+ @Rule
+ public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+ IssueLifecycle underTest = new IssueLifecycle(analysisMetadataHolder, issueChangeContext, workflow, updater, debtCalculator);
@Test
public void initNewOpenIssue() throws Exception {
DefaultIssue fromShort = new DefaultIssue();
fromShort.setResolution("resolution");
fromShort.setStatus("status");
- underTest.copyIssueAttributes(raw, fromShort);
+
+ Date commentDate = new Date();
+ fromShort.addComment(new DefaultIssueComment()
+ .setIssueKey("short")
+ .setCreatedAt(commentDate)
+ .setUserLogin("user")
+ .setMarkdownText("A comment"));
+
+ Branch branch = mock(Branch.class);
+ when(branch.getName()).thenReturn("master");
+ analysisMetadataHolder.setBranch(branch);
+
+ underTest.mergeConfirmedOrResolvedFromShortLivingBranch(raw, fromShort, "feature/foo");
assertThat(raw.resolution()).isEqualTo("resolution");
assertThat(raw.status()).isEqualTo("status");
+ assertThat(raw.changes().get(0).get(IssueFieldsSetter.FROM_SHORT_BRANCH).oldValue()).isEqualTo("feature/foo");
+ assertThat(raw.changes().get(0).get(IssueFieldsSetter.FROM_SHORT_BRANCH).newValue()).isEqualTo("master");
}
@Test
when(debtCalculator.calculate(raw)).thenReturn(DEFAULT_DURATION);
- underTest.copyExistingOpenIssueFromLongLivingBranch(raw, base);
+ Branch branch = mock(Branch.class);
+ when(branch.getName()).thenReturn("release-2.x");
+ analysisMetadataHolder.setBranch(branch);
+
+ underTest.copyExistingOpenIssueFromLongLivingBranch(raw, base, "master");
assertThat(raw.isNew()).isFalse();
assertThat(raw.isCopied()).isTrue();
assertThat(raw.debt()).isEqualTo(DEFAULT_DURATION);
assertThat(raw.isOnDisabledRule()).isTrue();
assertThat(raw.selectedAt()).isEqualTo(1000L);
+ assertThat(raw.changes().get(0).get(IssueFieldsSetter.FROM_LONG_BRANCH).oldValue()).isEqualTo("master");
+ assertThat(raw.changes().get(0).get(IssueFieldsSetter.FROM_LONG_BRANCH).newValue()).isEqualTo("release-2.x");
verifyZeroInteractions(updater);
}
@Test
public void do_nothing_if_no_new_issue() {
DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null);
- when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.singleton(newShortBranchIssue(i)));
+ when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.singleton(newShortBranchIssue(i, "myBranch")));
copier.tryMerge(component, Collections.emptyList());
verify(resolvedShortBranchIssuesLoader).loadCandidateIssuesForMergingInTargetBranch(component);
@Test
public void update_status_on_matches() {
DefaultIssue issue1 = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null);
- ShortBranchIssue shortBranchIssue = newShortBranchIssue(issue1);
+ ShortBranchIssue shortBranchIssue = newShortBranchIssue(issue1, "myBranch");
DefaultIssue newIssue = createIssue("issue2", "rule1", Issue.STATUS_OPEN, null);
when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.singleton(shortBranchIssue));
ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class);
verify(resolvedShortBranchIssuesLoader).loadDefaultIssuesWithChanges(captor.capture());
assertThat(captor.getValue()).containsOnly(shortBranchIssue);
- verify(issueLifecycle).copyIssueAttributes(newIssue, issue1);
+ verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(newIssue, issue1, "myBranch");
}
@Test
public void prefer_resolved_issues() {
- ShortBranchIssue shortBranchIssue1 = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null));
- ShortBranchIssue shortBranchIssue2 = newShortBranchIssue(createIssue("issue2", "rule1", Issue.STATUS_CONFIRMED, null));
+ ShortBranchIssue shortBranchIssue1 = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null), "myBranch1");
+ ShortBranchIssue shortBranchIssue2 = newShortBranchIssue(createIssue("issue2", "rule1", Issue.STATUS_CONFIRMED, null), "myBranch2");
DefaultIssue issue3 = createIssue("issue3", "rule1", Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE);
- ShortBranchIssue shortBranchIssue3 = newShortBranchIssue(issue3);
+ ShortBranchIssue shortBranchIssue3 = newShortBranchIssue(issue3, "myBranch3");
DefaultIssue newIssue = createIssue("newIssue", "rule1", Issue.STATUS_OPEN, null);
when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Arrays.asList(shortBranchIssue1, shortBranchIssue2, shortBranchIssue3));
when(resolvedShortBranchIssuesLoader.loadDefaultIssuesWithChanges(anyListOf(ShortBranchIssue.class))).thenReturn(ImmutableMap.of(shortBranchIssue3, issue3));
copier.tryMerge(component, Collections.singleton(newIssue));
- verify(issueLifecycle).copyIssueAttributes(newIssue, issue3);
+ verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(newIssue, issue3, "myBranch3");
}
private static DefaultIssue createIssue(String key, String ruleKey, String status, @Nullable String resolution) {
return issue;
}
- private ShortBranchIssue newShortBranchIssue(DefaultIssue i) {
- return new ShortBranchIssue(i.key(), i.line(), i.message(), i.getLineHash(), i.ruleKey(), i.status());
+ private ShortBranchIssue newShortBranchIssue(DefaultIssue i, String originBranch) {
+ return new ShortBranchIssue(i.key(), i.line(), i.message(), i.getLineHash(), i.ruleKey(), i.status(), originBranch);
}
}
</p>
);
}
+ if (diff.key === 'from_long_branch') {
+ return (
+ <p>
+ {translateWithParameters(
+ 'issue.change.from_long_branch',
+ diff.oldValue || '',
+ diff.newValue || ''
+ )}
+ </p>
+ );
+ }
+ if (diff.key === 'from_short_branch') {
+ return (
+ <p>
+ {translateWithParameters(
+ 'issue.change.from_short_branch',
+ diff.oldValue || '',
+ diff.newValue || ''
+ )}
+ </p>
+ );
+ }
let message;
if (diff.newValue != null) {
issue.changelog.changed_to={0} changed to {1}
issue.changelog.was=was {0}
issue.change.file_move=The file has been moved from {0} to {1}
+issue.change.from_long_branch=The issue has been copied from branch '{0}' to branch '{1}'
+issue.change.from_short_branch=The issue has been merged from branch '{0}' into branch '{1}'
issue.changelog.removed={0} removed
issue.changelog.field.severity=Severity
issue.changelog.field.actionPlan=Action Plan