]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9949 Copy all issue attributes when merging a short in a long living branch
authorJulien HENRY <julien.henry@sonarsource.com>
Thu, 12 Oct 2017 15:53:52 +0000 (17:53 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Fri, 20 Oct 2017 08:45:15 +0000 (18:45 +1000)
19 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/IssueChangeDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueChangeMapper.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/IssueChangeMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueChangeDaoTest.java
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/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/ShortBranchIssueStatusCopier.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStep.java
server/sonar-server/src/main/java/org/sonar/server/issue/IssueStorage.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/ShortBranchIssueStatusCopierTest.java
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueComment.java
sonar-core/src/main/java/org/sonar/core/issue/FieldDiffs.java

index 0e2f88f94b9e37f602ec1317447adede659afcbd..11095124c97a3674a19c73d9056af72669d7b49d 100644 (file)
@@ -27,20 +27,24 @@ import org.sonar.core.issue.tracking.Trackable;
 
 @Immutable
 public class ShortBranchIssue implements Trackable {
+  private final String key;
   private final Integer line;
   private final String message;
   private final String lineHash;
   private final RuleKey ruleKey;
   private final String status;
-  private final String resolution;
 
-  public ShortBranchIssue(@Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, @Nullable String resolution) {
+  public ShortBranchIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status) {
+    this.key = key;
     this.line = line;
     this.message = message;
     this.lineHash = lineHash;
     this.ruleKey = ruleKey;
     this.status = status;
-    this.resolution = resolution;
+  }
+
+  public String getKey() {
+    return key;
   }
 
   @CheckForNull
@@ -70,7 +74,24 @@ public class ShortBranchIssue implements Trackable {
     return status;
   }
 
-  public String getResolution() {
-    return resolution;
+  @Override
+  public int hashCode() {
+    return key.hashCode();
   }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null) {
+      return false;
+    }
+    if (getClass() != obj.getClass()) {
+      return false;
+    }
+    ShortBranchIssue other = (ShortBranchIssue) obj;
+    return key.equals(other.key);
+  }
+
 }
index 9a9fa026968caa50fd5c525b6f926634d723c152..eb22bc65c715eb6309a3a94c1c6f289c520e7fdd 100644 (file)
@@ -48,6 +48,10 @@ public class IssueChangeDao implements Dao {
     return executeLargeInputs(issueKeys, issueKeys1 -> mapper(session).selectByIssuesAndType(issueKeys1, changeType));
   }
 
+  public List<IssueChangeDto> selectByIssueKeys(DbSession session, Collection<String> issueKeys) {
+    return executeLargeInputs(issueKeys, issueKeys1 -> mapper(session).selectByIssues(issueKeys1));
+  }
+
   public Optional<IssueChangeDto> selectCommentByKey(DbSession session, String commentKey) {
     return Optional.ofNullable(mapper(session).selectByKeyAndType(commentKey, IssueChangeDto.TYPE_COMMENT));
   }
index 1d6db5b29943a81dfa77a1c4d89907fd28d54436..3b6aef0cff830d5539b89c5dbafceef681f2e92b 100644 (file)
@@ -40,5 +40,7 @@ public interface IssueChangeMapper {
   List<IssueChangeDto> selectByIssuesAndType(@Param("issueKeys") List<String> issueKeys,
     @Param("changeType") String changeType);
 
+  List<IssueChangeDto> selectByIssues(@Param("issueKeys") List<String> issueKeys);
+
   List<IssueChangeDto> selectChangelogOfNonClosedIssuesByComponent(@Param("componentUuid") String componentUuid, @Param("changeType") String changeType);
 }
index 3674cdc7af819b0245fed97ca3ffb817bb287864..3a68ea03af25b155fac0f4169ecaa87d2c6fa96e 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.db.issue;
 
-import com.google.common.base.Preconditions;
 import java.io.Serializable;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -27,9 +26,6 @@ import org.apache.commons.lang.builder.ToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.core.issue.ShortBranchIssue;
-import org.sonar.db.rule.RuleDefinitionDto;
-
-import static com.google.common.base.Preconditions.checkArgument;
 
 public final class ShortBranchIssueDto implements Serializable {
 
@@ -38,7 +34,6 @@ public final class ShortBranchIssueDto implements Serializable {
   private Integer line;
   private String checksum;
   private String status;
-  private String resolution;
 
   // joins
   private String ruleKey;
@@ -82,16 +77,6 @@ public final class ShortBranchIssueDto implements Serializable {
     return this;
   }
 
-  @CheckForNull
-  public String getResolution() {
-    return resolution;
-  }
-
-  public ShortBranchIssueDto setResolution(@Nullable String s) {
-    this.resolution = s;
-    return this;
-  }
-
   @CheckForNull
   public String getChecksum() {
     return checksum;
@@ -120,6 +105,6 @@ public final class ShortBranchIssueDto implements Serializable {
   }
 
   public static ShortBranchIssue toShortBranchIssue(ShortBranchIssueDto dto) {
-    return new ShortBranchIssue(dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getResolution());
+    return new ShortBranchIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus());
   }
 }
index 46a732b07196f2965e102409425a0c607101f237..8b315466eb75f815235d9bdcd1d7228aa0e518de 100644 (file)
     </foreach>
     order by c.created_at
   </select>
+  
+  <select id="selectByIssues" parameterType="map" resultType="IssueChange">
+    select
+    <include refid="issueChangeColumns"/>
+    from issue_changes c
+    where c.issue_key in
+    <foreach collection="issueKeys" open="(" close=")" item="key" separator=",">
+      #{key,jdbcType=VARCHAR}
+    </foreach>
+  </select>
 
   <select id="selectByKeyAndType" parameterType="map" resultType="IssueChange">
     select
index fa9c34ec2d07baaabb79fbd362e77a8f011628a8..8c365703f9e33e1445f02881a8315c09d2a04160 100644 (file)
       i.message as message,
       i.line as line,
       i.status as status,
-      i.resolution as resolution,
       i.checksum as checksum,
       r.plugin_rule_key as ruleKey,
       r.plugin_name as ruleRepo
index a5b46eb3c15d959c5b9d8f14b9f7279964df9dae..7a0ffca2a48df4d72f8eba95fbda92bc2a891c38 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.db.issue;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import org.junit.Rule;
@@ -48,6 +49,14 @@ public class IssueChangeDaoTest {
     assertThat(changelog.get(0).diffs().get("severity").oldValue()).isEqualTo("MAJOR");
   }
 
+  @Test
+  public void select_issue_changes_from_issues_key() {
+    db.prepareDbUnit(getClass(), "shared.xml");
+
+    List<IssueChangeDto> changelog = underTest.selectByIssueKeys(db.getSession(), Arrays.asList("1000", "1001"));
+    assertThat(changelog).hasSize(5);
+  }
+
   @Test
   public void selectChangelogOfNonClosedIssuesByComponent() {
     db.prepareDbUnit(getClass(), "selectChangelogOfNonClosedIssuesByComponent.xml");
index f44788c174f06a646d71ba000d394bacc768122e..8fc59751ee0cb4f88e451ea1b80a5ab862587415 100644 (file)
@@ -30,7 +30,6 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.System2;
-import org.sonar.core.issue.ShortBranchIssue;
 import org.sonar.db.DbTester;
 import org.sonar.db.RowNotFoundException;
 import org.sonar.db.component.ComponentDto;
@@ -212,7 +211,6 @@ public class IssueDaoTest {
     assertThat(fp.getChecksum()).isEqualTo(fpIssue.getChecksum());
     assertThat(fp.getRuleKey()).isEqualTo(fpIssue.getRuleKey());
     assertThat(fp.getStatus()).isEqualTo(fpIssue.getStatus());
-    assertThat(fp.getResolution()).isEqualTo(fpIssue.getResolution());
 
     assertThat(fp.getLine()).isNotNull();
     assertThat(fp.getLine()).isNotZero();
@@ -221,7 +219,6 @@ public class IssueDaoTest {
     assertThat(fp.getChecksum()).isNotEmpty();
     assertThat(fp.getRuleKey()).isNotNull();
     assertThat(fp.getStatus()).isNotNull();
-    assertThat(fp.getResolution()).isNotNull();
   }
 
   private static IssueDto newIssueDto(String key) {
index 26db336fcf50556a604bac3faba7064e6a402d84..a96d8f2d7e9d840d48c2986d9636b58b31a40fdc 100644 (file)
@@ -67,10 +67,10 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
     }
   }
 
-  private void fillNewOpenIssues(Component component, Iterable<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+  private void fillNewOpenIssues(Component component, Iterable<DefaultIssue> newIssues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
     List<DefaultIssue> list = new ArrayList<>();
 
-    issues.forEach(issue -> {
+    newIssues.forEach(issue -> {
       issueLifecycle.initNewOpenIssue(issue);
       list.add(issue);
     });
@@ -92,7 +92,7 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
     for (Map.Entry<DefaultIssue, DefaultIssue> entry : matched.entrySet()) {
       DefaultIssue raw = entry.getKey();
       DefaultIssue base = entry.getValue();
-      issueLifecycle.copyExistingOpenIssue(raw, base);
+      issueLifecycle.copyExistingOpenIssueFromLongLivingBranch(raw, base);
       process(component, raw, cacheAppender);
     }
   }
index 868e6db058058fa0f8086a4bad41826bae385ebf..7f88d3404e0819737513b1ab75c88b1dc718bb48 100644 (file)
@@ -21,9 +21,10 @@ package org.sonar.server.computation.task.projectanalysis.issue;
 
 import com.google.common.annotations.VisibleForTesting;
 import java.util.Date;
-import javax.annotation.Nullable;
 import org.sonar.api.issue.Issue;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueComment;
+import org.sonar.core.issue.FieldDiffs;
 import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.core.util.Uuids;
 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
@@ -65,7 +66,7 @@ public class IssueLifecycle {
     issue.setEffort(debtCalculator.calculate(issue));
   }
 
-  public void copyExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
+  public void copyExistingOpenIssueFromLongLivingBranch(DefaultIssue raw, DefaultIssue base) {
     raw.setKey(Uuids.create());
     raw.setNew(false);
     raw.setCopied(true);
@@ -77,9 +78,21 @@ public class IssueLifecycle {
     }
   }
 
-  public void copyResolution(DefaultIssue raw, String status, @Nullable String resolution) {
-    raw.setStatus(status);
-    raw.setResolution(resolution);
+  public void mergeIssueFromShortLivingBranch(DefaultIssue raw, DefaultIssue fromShortLiving) {
+    raw.setCopied(true);
+    raw.setType(fromShortLiving.type());
+    raw.setResolution(fromShortLiving.resolution());
+    raw.setStatus(fromShortLiving.status());
+    raw.setAssignee(fromShortLiving.assignee());
+    raw.setAuthorLogin(fromShortLiving.authorLogin());
+    raw.setTags(fromShortLiving.tags());
+    raw.setAttributes(fromShortLiving.attributes());
+    if (fromShortLiving.manualSeverity()) {
+      raw.setManualSeverity(true);
+      raw.setSeverity(fromShortLiving.severity());
+    }
+    fromShortLiving.comments().forEach(c -> raw.addComment(DefaultIssueComment.copy(raw.key(), c)));
+    fromShortLiving.changes().forEach(c -> raw.addChange(FieldDiffs.copy(raw.key(), c)));
   }
 
   public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
index 8b49759aae64eee1c540ce7fe56352514188571f..3944af7c101c550cc2602c07b95d9f443e2f087d 100644 (file)
@@ -28,7 +28,7 @@ import org.sonar.core.issue.tracking.Tracking;
 import org.sonar.server.computation.task.projectanalysis.component.Component;
 
 public class ShortBranchIssueStatusCopier {
-  private final ShortBranchIssuesLoader resolvedShortBranchIssuesLoader;
+  private final ShortBranchIssuesLoader shortBranchIssuesLoader;
   private final SimpleTracker<DefaultIssue, ShortBranchIssue> tracker;
   private final IssueLifecycle issueLifecycle;
 
@@ -36,20 +36,23 @@ public class ShortBranchIssueStatusCopier {
     this(resolvedShortBranchIssuesLoader, new SimpleTracker<>(), issueLifecycle);
   }
 
-  public ShortBranchIssueStatusCopier(ShortBranchIssuesLoader resolvedShortBranchIssuesLoader, SimpleTracker<DefaultIssue, ShortBranchIssue> tracker,
-    IssueLifecycle issueLifecycle) {
-    this.resolvedShortBranchIssuesLoader = resolvedShortBranchIssuesLoader;
+  public ShortBranchIssueStatusCopier(ShortBranchIssuesLoader shortBranchIssuesLoader, SimpleTracker<DefaultIssue, ShortBranchIssue> tracker, IssueLifecycle issueLifecycle) {
+    this.shortBranchIssuesLoader = shortBranchIssuesLoader;
     this.tracker = tracker;
     this.issueLifecycle = issueLifecycle;
   }
 
   public void updateStatus(Component component, Collection<DefaultIssue> newIssues) {
-    Collection<ShortBranchIssue> shortBranchIssues = resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component);
+    Collection<ShortBranchIssue> shortBranchIssues = shortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component);
     Tracking<DefaultIssue, ShortBranchIssue> tracking = tracker.track(newIssues, shortBranchIssues);
 
-    for (Map.Entry<DefaultIssue, ShortBranchIssue> e : tracking.getMatchedRaws().entrySet()) {
+    Map<DefaultIssue, ShortBranchIssue> matchedRaws = tracking.getMatchedRaws();
+
+    Map<ShortBranchIssue, DefaultIssue> defaultIssues = shortBranchIssuesLoader.loadDefaultIssuesWithChanges(matchedRaws.values());
+
+    for (Map.Entry<DefaultIssue, ShortBranchIssue> e : matchedRaws.entrySet()) {
       ShortBranchIssue issue = e.getValue();
-      issueLifecycle.copyResolution(e.getKey(), issue.getStatus(), issue.getResolution());
+      issueLifecycle.mergeIssueFromShortLivingBranch(e.getKey(), defaultIssues.get(issue));
     }
   }
 }
index ebac0c2f982e040f1bebf6fdea88f5d39c43af31..acc598059a6d1cf7ffbc34ec8a57b40c61924b29 100644 (file)
@@ -21,16 +21,24 @@ package org.sonar.server.computation.task.projectanalysis.issue;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.ShortBranchIssue;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueDto;
 import org.sonar.db.issue.ShortBranchIssueDto;
 import org.sonar.server.computation.task.projectanalysis.component.Component;
 import org.sonar.server.computation.task.projectanalysis.component.ShortBranchComponentsWithIssues;
 
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toMap;
+
 public class ShortBranchIssuesLoader {
 
   private final ShortBranchComponentsWithIssues shortBranchComponentsWithIssues;
@@ -54,4 +62,37 @@ public class ShortBranchIssuesLoader {
         .collect(Collectors.toList());
     }
   }
+
+  public Map<ShortBranchIssue, DefaultIssue> loadDefaultIssuesWithChanges(Collection<ShortBranchIssue> lightIssues) {
+    if (lightIssues.isEmpty()) {
+      return Collections.emptyMap();
+    }
+    Map<String, ShortBranchIssue> issuesByKey = lightIssues.stream().collect(Collectors.toMap(ShortBranchIssue::getKey, i -> i));
+    try (DbSession session = dbClient.openSession(false)) {
+
+      Map<String, List<IssueChangeDto>> changeDtoByIssueKey = dbClient.issueChangeDao()
+        .selectByIssueKeys(session, issuesByKey.keySet()).stream().collect(groupingBy(IssueChangeDto::getIssueKey));
+
+      return dbClient.issueDao().selectByKeys(session, issuesByKey.keySet())
+        .stream()
+        .map(IssueDto::toDefaultIssue)
+        .peek(i -> setChanges(changeDtoByIssueKey, i))
+        .collect(toMap(i -> issuesByKey.get(i.key()), i -> i));
+    }
+  }
+
+  private static void setChanges(Map<String, List<IssueChangeDto>> changeDtoByIssueKey, DefaultIssue i) {
+    changeDtoByIssueKey.get(i.key()).forEach(c -> {
+      switch (c.getChangeType()) {
+        case IssueChangeDto.TYPE_FIELD_CHANGE:
+          i.addChange(c.toFieldDiffs());
+          break;
+        case IssueChangeDto.TYPE_COMMENT:
+          i.addComment(c.toComment());
+          break;
+        default:
+          throw new IllegalStateException("Unknow change type: " + c.getChangeType());
+      }
+    });
+  }
 }
index 6276bf44b25fd3751432e0e05d66b136f44bdbe3..27d5128b5aa730ac92beb96a39b74737398a1414 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis.step;
 
-import org.sonar.api.issue.IssueComment;
 import org.sonar.api.utils.System2;
 import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.DefaultIssueComment;
-import org.sonar.core.issue.FieldDiffs;
 import org.sonar.core.util.CloseableIterator;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.issue.IssueChangeDto;
 import org.sonar.db.issue.IssueChangeMapper;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.issue.IssueMapper;
@@ -35,6 +31,7 @@ import org.sonar.server.computation.task.projectanalysis.issue.IssueCache;
 import org.sonar.server.computation.task.projectanalysis.issue.RuleRepository;
 import org.sonar.server.computation.task.projectanalysis.issue.UpdateConflictResolver;
 import org.sonar.server.computation.task.step.ComputationStep;
+import org.sonar.server.issue.IssueStorage;
 
 public class PersistIssuesStep implements ComputationStep {
 
@@ -64,7 +61,7 @@ public class PersistIssuesStep implements ComputationStep {
         DefaultIssue issue = issues.next();
         boolean saved = persistIssueIfRequired(mapper, issue);
         if (saved) {
-          insertChanges(changeMapper, issue);
+          IssueStorage.insertChanges(changeMapper, issue);
         }
       }
       dbSession.flushStatements();
@@ -101,21 +98,6 @@ public class PersistIssuesStep implements ComputationStep {
     }
   }
 
-  private static void insertChanges(IssueChangeMapper mapper, DefaultIssue issue) {
-    for (IssueComment comment : issue.comments()) {
-      DefaultIssueComment c = (DefaultIssueComment) comment;
-      if (c.isNew()) {
-        IssueChangeDto changeDto = IssueChangeDto.of(c);
-        mapper.insert(changeDto);
-      }
-    }
-    FieldDiffs diffs = issue.currentChange();
-    if (!issue.isNew() && diffs != null) {
-      IssueChangeDto changeDto = IssueChangeDto.of(issue.key(), diffs);
-      mapper.insert(changeDto);
-    }
-  }
-
   @Override
   public String getDescription() {
     return "Persist issues";
index 6c02b9c1a0ac6221236443a85b57274b571836fe..6b186a724119e56b987c63b9a61fe055a3993699 100644 (file)
@@ -152,7 +152,7 @@ public abstract class IssueStorage {
 
   protected abstract IssueDto doUpdate(DbSession batchSession, long now, DefaultIssue issue);
 
-  private void insertChanges(IssueChangeMapper mapper, DefaultIssue issue) {
+  public static void insertChanges(IssueChangeMapper mapper, DefaultIssue issue) {
     for (IssueComment comment : issue.comments()) {
       DefaultIssueComment c = (DefaultIssueComment) comment;
       if (c.isNew()) {
@@ -164,6 +164,11 @@ public abstract class IssueStorage {
     if (!issue.isNew() && diffs != null) {
       IssueChangeDto changeDto = IssueChangeDto.of(issue.key(), diffs);
       mapper.insert(changeDto);
+    } else if (issue.isCopied()) {
+      for (FieldDiffs d : issue.changes()) {
+        IssueChangeDto changeDto = IssueChangeDto.of(issue.key(), d);
+        mapper.insert(changeDto);
+      }
     }
   }
 
index bdd6a058bffd57ae48afb9e813196c7d5552e0dc..a588db622b50fcced977eabeeb7248c2151f33b0 100644 (file)
@@ -76,11 +76,14 @@ public class IssueLifecycleTest {
   }
 
   @Test
-  public void copyResolution() {
-    DefaultIssue issue = new DefaultIssue();
-    underTest.copyResolution(issue, "status", "resolution");
-    assertThat(issue.resolution()).isEqualTo("resolution");
-    assertThat(issue.status()).isEqualTo("status");
+  public void mergeIssueFromShortLivingBranch() {
+    DefaultIssue raw = new DefaultIssue();
+    DefaultIssue fromShort = new DefaultIssue();
+    fromShort.setResolution("resolution");
+    fromShort.setStatus("status");
+    underTest.mergeIssueFromShortLivingBranch(raw, fromShort);
+    assertThat(raw.resolution()).isEqualTo("resolution");
+    assertThat(raw.status()).isEqualTo("status");
   }
 
   @Test
@@ -120,7 +123,7 @@ public class IssueLifecycleTest {
 
     when(debtCalculator.calculate(raw)).thenReturn(DEFAULT_DURATION);
 
-    underTest.copyExistingOpenIssue(raw, base);
+    underTest.copyExistingOpenIssueFromLongLivingBranch(raw, base);
 
     assertThat(raw.isNew()).isFalse();
     assertThat(raw.isCopied()).isTrue();
index c5948cfc60b2eab12b43faa745275dd5bb0c9f22..d244d44cb6a3469cf4df0a9b11f248358f083bf0 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis.issue;
 
+import com.google.common.collect.ImmutableMap;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import javax.annotation.Nullable;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.sonar.api.issue.Issue;
@@ -33,6 +36,8 @@ import org.sonar.core.issue.ShortBranchIssue;
 import org.sonar.core.issue.tracking.SimpleTracker;
 import org.sonar.server.computation.task.projectanalysis.component.Component;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyListOf;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -76,24 +81,31 @@ public class ShortBranchIssueStatusCopierTest {
 
   @Test
   public void update_status_on_matches() {
-    ShortBranchIssue shortBranchIssue = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null));
+    DefaultIssue issue1 = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null);
+    ShortBranchIssue shortBranchIssue = newShortBranchIssue(issue1);
     DefaultIssue newIssue = createIssue("issue2", "rule1", Issue.STATUS_OPEN, null);
 
     when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.singleton(shortBranchIssue));
+    when(resolvedShortBranchIssuesLoader.loadDefaultIssuesWithChanges(anyListOf(ShortBranchIssue.class))).thenReturn(ImmutableMap.of(shortBranchIssue, issue1));
     copier.updateStatus(component, Collections.singleton(newIssue));
-    verify(issueLifecycle).copyResolution(newIssue, shortBranchIssue.getStatus(), shortBranchIssue.getResolution());
+    ArgumentCaptor<Collection> captor = ArgumentCaptor.forClass(Collection.class);
+    verify(resolvedShortBranchIssuesLoader).loadDefaultIssuesWithChanges(captor.capture());
+    assertThat(captor.getValue()).containsOnly(shortBranchIssue);
+    verify(issueLifecycle).mergeIssueFromShortLivingBranch(newIssue, issue1);
   }
 
   @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 shortBranchIssue3 = newShortBranchIssue(createIssue("issue3", "rule1", Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE));
+    DefaultIssue issue3 = createIssue("issue3", "rule1", Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE);
+    ShortBranchIssue shortBranchIssue3 = newShortBranchIssue(issue3);
     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.updateStatus(component, Collections.singleton(newIssue));
-    verify(issueLifecycle).copyResolution(newIssue, Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE);
+    verify(issueLifecycle).mergeIssueFromShortLivingBranch(newIssue, issue3);
   }
 
   private static DefaultIssue createIssue(String key, String ruleKey, String status, @Nullable String resolution) {
@@ -108,6 +120,6 @@ public class ShortBranchIssueStatusCopierTest {
   }
 
   private ShortBranchIssue newShortBranchIssue(DefaultIssue i) {
-    return new ShortBranchIssue(i.line(), i.message(), i.getLineHash(), i.ruleKey(), i.status(), i.resolution());
+    return new ShortBranchIssue(i.key(), i.line(), i.message(), i.getLineHash(), i.ruleKey(), i.status());
   }
 }
index fd729489a70e7c1bc9eb17ca64e0067450d883a1..02032a1c62dcda9378af53709e199539c513540e 100644 (file)
@@ -97,7 +97,7 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
   // true if the issue did not exist in the previous scan.
   private boolean isNew = true;
 
-  // true if the issue is being copied to a different branch
+  // true if the issue is being copied between branch
   private boolean isCopied = false;
 
   // True if the issue did exist in the previous scan but not in the current one. That means
index 57fd9cd714da102c75844902e1a5b7b4810e61d8..df964e689899f7dc60319015f5b08d031b867fd7 100644 (file)
@@ -53,6 +53,20 @@ public class DefaultIssueComment implements Serializable, IssueComment {
     return comment;
   }
 
+  /**
+   * Copy a comment from another issue
+   */
+  public static DefaultIssueComment copy(String issueKey, IssueComment c) {
+    DefaultIssueComment comment = new DefaultIssueComment();
+    comment.setIssueKey(issueKey);
+    comment.setKey(Uuids.create());
+    comment.setUserLogin(c.userLogin());
+    comment.setMarkdownText(c.markdownText());
+    comment.setCreatedAt(c.createdAt()).setUpdatedAt(c.updatedAt());
+    comment.setNew(true);
+    return comment;
+  }
+
   @Override
   public String markdownText() {
     return markdownText;
@@ -125,4 +139,5 @@ public class DefaultIssueComment implements Serializable, IssueComment {
     isNew = b;
     return this;
   }
+
 }
index 6081c975a3edd53044847c56479601cd707c20d5..80c7b880fa0a81fc13ac9662f6ba5a410e9547b1 100644 (file)
@@ -45,6 +45,18 @@ public class FieldDiffs implements Serializable {
 
   private final Map<String, Diff> diffs = Maps.newLinkedHashMap();
 
+  /**
+   * Copy a diff from another issue
+   */
+  public static FieldDiffs copy(String issueKey, FieldDiffs c) {
+    FieldDiffs result = new FieldDiffs();
+    result.issueKey = issueKey;
+    result.userLogin = c.userLogin;
+    result.creationDate = c.creationDate;
+    result.diffs.putAll(c.diffs);
+    return result;
+  }
+
   public Map<String, Diff> diffs() {
     return diffs;
   }
@@ -195,4 +207,5 @@ public class FieldDiffs implements Serializable {
       return sb.toString();
     }
   }
+
 }