]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9987 Add a changelog entry when copying/merging issues
authorJulien HENRY <julien.henry@sonarsource.com>
Mon, 16 Oct 2017 14:25:09 +0000 (16:25 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Fri, 20 Oct 2017 08:45:15 +0000 (18:45 +1000)
15 files changed:
server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/ShortBranchIssueDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitor.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycle.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMerger.java
server/sonar-server/src/main/java/org/sonar/server/issue/IssueFieldsSetter.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/ChangelogAction.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycleTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMergerTest.java
server/sonar-web/src/main/js/components/issue/components/IssueChangelogDiff.js
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 11095124c97a3674a19c73d9056af72669d7b49d..f6c6bcd977b1c82874b6e0c46c9a62665ccee8b5 100644 (file)
@@ -33,14 +33,16 @@ public class ShortBranchIssue implements Trackable {
   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() {
@@ -74,6 +76,10 @@ public class ShortBranchIssue implements Trackable {
     return status;
   }
 
+  public String getBranchName() {
+    return branchName;
+  }
+
   @Override
   public int hashCode() {
     return key.hashCode();
index 3a68ea03af25b155fac0f4169ecaa87d2c6fa96e..3c1901041e4c5ceebcb7bc37c44e6abb2b44c941 100644 (file)
@@ -38,6 +38,7 @@ public final class ShortBranchIssueDto implements Serializable {
   // joins
   private String ruleKey;
   private String ruleRepo;
+  private String branchName;
 
   public String getKey() {
     return kee;
@@ -68,6 +69,15 @@ public final class ShortBranchIssueDto implements Serializable {
     return this;
   }
 
+  public String getBranchName() {
+    return branchName;
+  }
+
+  public ShortBranchIssueDto setBranchName(String s) {
+    this.branchName = s;
+    return this;
+  }
+
   public String getStatus() {
     return status;
   }
@@ -105,6 +115,6 @@ public final class ShortBranchIssueDto implements Serializable {
   }
 
   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());
   }
 }
index 8c365703f9e33e1445f02881a8315c09d2a04160..09d434355027ce267359d9d70c4a40376d48be86 100644 (file)
       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}
index 5201db84197c9250e84070e3488ac265a853e800..8c4682215c153f6bf4e26c63aa3155e88b77d407 100644 (file)
@@ -33,6 +33,7 @@ import org.sonar.api.rule.RuleKey;
 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;
@@ -185,14 +186,19 @@ public class IssueDaoTest {
   @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")
@@ -202,9 +208,13 @@ public class IssueDaoTest {
   @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());
index 9817dadbfdf43f5cb70298f19894431ab39d33c9..e91d1fc8c4a4b8ae2c3c363daee2774522f110a7 100644 (file)
@@ -37,31 +37,38 @@ public class MergeBranchComponentUuids {
   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);
   }
index e0c9bd92fc1eea07a682ed8bed13e80add1b492e..d381db8fa363e29b62bd2c7842387ef3483b3e19 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.core.issue.DefaultIssue;
 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;
 
@@ -39,9 +40,11 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
   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;
@@ -49,6 +52,7 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
     this.analysisMetadataHolder = analysisMetadataHolder;
     this.issueTracking = issueTracking;
     this.issueStatusCopier = issueStatusCopier;
+    this.mergeBranchComponentUuids = mergeBranchComponentUuids;
   }
 
   @Override
@@ -92,7 +96,7 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
     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);
     }
   }
index 6b40c04feb47b13929df7d1ca37b4ebc356748ac..0032206793591dbe1965a766509359a17ee52332 100644 (file)
@@ -45,13 +45,16 @@ public class IssueLifecycle {
   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;
@@ -66,13 +69,19 @@ public class IssueLifecycle {
     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()) {
index 79d5e5c2a776e36a6bd082e385647c1448ffaa53..41d55e8570417b0789e633309384dfaff79af7a7 100644 (file)
@@ -56,7 +56,7 @@ public class ShortBranchIssueMerger {
 
     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());
     }
   }
 }
index a1de892c458c34bed6a5bf179317611942d80fa7..1bfdd848ce867aee910fa242335841bcdb4ffa47 100644 (file)
@@ -58,6 +58,8 @@ public class IssueFieldsSetter {
   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
index 35542267b9bf1319ac75cab925f8ecbaa89502ad..b1eebdecdd63e94494e6beb397c128b8f4f41b7e 100644 (file)
@@ -175,5 +175,6 @@ public class ChangelogAction implements IssuesWsAction {
       ComponentDto file = files.get(fileUuid);
       return file == null ? null : file.longName();
     }
+
   }
 }
index 1427302612c78569572bc8f8783eb733041c9858..0f695c19d65f4dedd017b7e417f48a2f3bcfc2dd 100644 (file)
@@ -119,6 +119,8 @@ public class IntegrateIssuesVisitorTest {
   private MergeBranchComponentUuids mergeBranchComponentsUuids;
   @Mock
   private ShortBranchIssueMerger issueStatusCopier;
+  @Mock
+  private MergeBranchComponentUuids mergeBranchComponentUuids;
 
   ArgumentCaptor<DefaultIssue> defaultIssueCaptor;
 
@@ -150,7 +152,7 @@ public class IntegrateIssuesVisitorTest {
     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
@@ -258,6 +260,7 @@ public class IntegrateIssuesVisitorTest {
   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);
@@ -284,7 +287,7 @@ public class IntegrateIssuesVisitorTest {
 
     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);
 
index ecd79be561777a2db56cfb2d1474344bac8bf47a..5b13b2ea7f84c4d8a728488ccd3258e6062ed7dd 100644 (file)
@@ -21,12 +21,16 @@ package org.sonar.server.computation.task.projectanalysis.issue;
 
 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;
 
@@ -57,7 +61,10 @@ public class IssueLifecycleTest {
 
   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 {
@@ -81,9 +88,23 @@ public class IssueLifecycleTest {
     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
@@ -123,7 +144,11 @@ public class IssueLifecycleTest {
 
     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();
@@ -140,6 +165,8 @@ public class IssueLifecycleTest {
     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);
   }
index fcda03e282c2cc7bd86f022209cbea59f1d31d8b..134f83b9d4eedb4e3d87f8f5ed47c3aa33ada077 100644 (file)
@@ -72,7 +72,7 @@ public class ShortBranchIssueMergerTest {
   @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);
@@ -82,7 +82,7 @@ public class ShortBranchIssueMergerTest {
   @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));
@@ -91,21 +91,21 @@ public class ShortBranchIssueMergerTest {
     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) {
@@ -119,7 +119,7 @@ public class ShortBranchIssueMergerTest {
     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);
   }
 }
index e0b1a93556bad18b6d49a28cfbb99c1f329264df..f858a13810a275475eddcc7b03fa1ee0e04c4d68 100644 (file)
@@ -43,6 +43,28 @@ export default function IssueChangelogDiff(props /*: { diff: ChangelogDiff } */)
       </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) {
index 970c842424655af9ae12f5a097cc767e0361542b..06b017e85f6da7c61606eb0b6ebb8be9a038fa00 100644 (file)
@@ -607,6 +607,8 @@ issues.my_issues=My Issues
 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