]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11858 Apply light issue tracking with siblings for all branches
authorJulien HENRY <julien.henry@sonarsource.com>
Tue, 26 Mar 2019 15:36:26 +0000 (16:36 +0100)
committerSonarTech <sonartech@sonarsource.com>
Tue, 23 Apr 2019 18:21:07 +0000 (20:21 +0200)
26 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ShortBranchComponentsWithIssues.java [deleted file]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssues.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssue.java [deleted file]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssueMerger.java [deleted file]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssuesLoader.java [deleted file]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingIssue.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMerger.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssuesLoader.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssueMergerTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/ShortBranchIssueDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.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/IssueDaoTest.java
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java
sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java
sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java

diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ShortBranchComponentsWithIssues.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ShortBranchComponentsWithIssues.java
deleted file mode 100644 (file)
index 1394247..0000000
+++ /dev/null
@@ -1,66 +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 java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-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.
- *
- */
-public class ShortBranchComponentsWithIssues {
-  private final String uuid;
-  private final DbClient dbClient;
-
-  private Map<String, Set<String>> uuidsByKey;
-
-  public ShortBranchComponentsWithIssues(TreeRootHolder treeRootHolder, DbClient dbClient) {
-    this.uuid = treeRootHolder.getRoot().getUuid();
-    this.dbClient = dbClient;
-  }
-
-  private void loadUuidsByKey() {
-    uuidsByKey = new HashMap<>();
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      List<KeyWithUuidDto> components = dbClient.componentDao().selectComponentKeysHavingIssuesToMerge(dbSession, uuid);
-      for (KeyWithUuidDto dto : components) {
-        uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid());
-      }
-    }
-  }
-
-  public Set<String> getUuids(String componentKey) {
-    if (uuidsByKey == null) {
-      loadUuidsByKey();
-    }
-
-    return uuidsByKey.getOrDefault(componentKey, Collections.emptySet());
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssues.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssues.java
new file mode 100644 (file)
index 0000000..cce1c2d
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+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;
+
+import static org.sonar.db.component.ComponentDto.removeBranchAndPullRequestFromKey;
+
+/**
+ * Cache a map of component key -> set&lt;uuid&gt; in sibling branches/PR that have open issues
+ *
+ */
+public class SiblingComponentsWithOpenIssues {
+  private final DbClient dbClient;
+  private final AnalysisMetadataHolder metadataHolder;
+  private final TreeRootHolder treeRootHolder;
+
+  private Map<String, Set<String>> uuidsByKey;
+
+  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().selectAllSiblingComponentKeysHavingOpenIssues(dbSession,
+        metadataHolder.getBranch().getMergeBranchUuid().orElse(currentBranchUuid), currentBranchUuid);
+      for (KeyWithUuidDto dto : components) {
+        uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid());
+      }
+    }
+  }
+
+  public Set<String> getUuids(String componentKey) {
+    if (uuidsByKey == null) {
+      loadUuidsByKey();
+    }
+
+    return uuidsByKey.getOrDefault(componentKey, Collections.emptySet());
+  }
+}
index 3298ce5abc0b1554fe1bfbb2f908bd2b662a2d57..776e9d63b9ae9ed6cb963391bc244441e8aec9ee 100644 (file)
@@ -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,
index bf04ad5d4a01ca8898c630d3aea35242fe89191d..b048a138b9fac6482aa3bfee9268779ffab80564 100644 (file)
@@ -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);
index e8f046e8f062a55ce84cd089cfa2ccd4eb513ca0..55c4305918fb6a258b09d3e50a5a3d8c2af6b487 100644 (file)
@@ -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) {
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssue.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssue.java
deleted file mode 100644 (file)
index 3f4b4d6..0000000
+++ /dev/null
@@ -1,111 +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.issue;
-
-import java.util.Date;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import org.sonar.api.rule.RuleKey;
-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 branchName;
-  private final Date creationDate;
-
-  public ShortBranchIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName, Date creationDate) {
-    this.key = key;
-    this.line = line;
-    this.message = message;
-    this.lineHash = lineHash;
-    this.ruleKey = ruleKey;
-    this.status = status;
-    this.branchName = branchName;
-    this.creationDate = creationDate;
-  }
-
-  public String getKey() {
-    return key;
-  }
-
-  @CheckForNull
-  @Override
-  public Integer getLine() {
-    return line;
-  }
-
-  @Override
-  public String getMessage() {
-    return message;
-  }
-
-  @CheckForNull
-  @Override
-  public String getLineHash() {
-    return lineHash;
-  }
-
-  @Override
-  public RuleKey getRuleKey() {
-    return ruleKey;
-  }
-
-  @Override
-  public String getStatus() {
-    return status;
-  }
-
-  public String getBranchName() {
-    return branchName;
-  }
-
-  @Override
-  public Date getCreationDate() {
-    return creationDate;
-  }
-
-  @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);
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssueMerger.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssueMerger.java
deleted file mode 100644 (file)
index 2088df0..0000000
+++ /dev/null
@@ -1,62 +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.issue;
-
-import java.util.Collection;
-import java.util.Map;
-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;
-  private final IssueLifecycle issueLifecycle;
-
-  public ShortBranchIssueMerger(ShortBranchIssuesLoader resolvedShortBranchIssuesLoader, IssueLifecycle issueLifecycle) {
-    this(resolvedShortBranchIssuesLoader, new SimpleTracker<>(), issueLifecycle);
-  }
-
-  public ShortBranchIssueMerger(ShortBranchIssuesLoader shortBranchIssuesLoader, SimpleTracker<DefaultIssue, ShortBranchIssue> tracker, IssueLifecycle issueLifecycle) {
-    this.shortBranchIssuesLoader = shortBranchIssuesLoader;
-    this.tracker = tracker;
-    this.issueLifecycle = issueLifecycle;
-  }
-
-  /**
-   * Look for all resolved/confirmed issues in short living branches targeting the current 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);
-
-    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.mergeConfirmedOrResolvedFromShortLivingBranch(e.getKey(), defaultIssues.get(issue), issue.getBranchName());
-    }
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssuesLoader.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssuesLoader.java
deleted file mode 100644 (file)
index 3932326..0000000
+++ /dev/null
@@ -1,91 +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.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.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ShortBranchComponentsWithIssues;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.issue.ShortBranchIssueDto;
-
-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 {
-
-  private final ShortBranchComponentsWithIssues shortBranchComponentsWithIssues;
-  private final DbClient dbClient;
-  private final ComponentIssuesLoader componentIssuesLoader;
-
-  public ShortBranchIssuesLoader(ShortBranchComponentsWithIssues shortBranchComponentsWithIssues, DbClient dbClient,
-    ComponentIssuesLoader componentIssuesLoader) {
-    this.shortBranchComponentsWithIssues = shortBranchComponentsWithIssues;
-    this.dbClient = dbClient;
-    this.componentIssuesLoader = componentIssuesLoader;
-  }
-
-  public Collection<ShortBranchIssue> loadCandidateIssuesForMergingInTargetBranch(Component component) {
-    String componentKey = ComponentDto.removeBranchAndPullRequestFromKey(component.getDbKey());
-    Set<String> uuids = shortBranchComponentsWithIssues.getUuids(componentKey);
-    if (uuids.isEmpty()) {
-      return Collections.emptyList();
-    }
-
-    try (DbSession session = dbClient.openSession(false)) {
-      return dbClient.issueDao().selectOpenByComponentUuids(session, uuids)
-        .stream()
-        .map(ShortBranchIssuesLoader::toShortBranchIssue)
-        .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()));
-  }
-
-  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)) {
-      List<DefaultIssue> issues = dbClient.issueDao().selectByKeys(session, issuesByKey.keySet())
-        .stream()
-        .map(IssueDto::toDefaultIssue)
-        .collect(toList(issuesByKey.size()));
-      componentIssuesLoader.loadChanges(session, issues);
-      return issues.stream()
-        .collect(uniqueIndex(i -> issuesByKey.get(i.key()), i -> i, issues.size()));
-    }
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingIssue.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingIssue.java
new file mode 100644 (file)
index 0000000..438fd46
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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.issue;
+
+import java.util.Date;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.tracking.Trackable;
+
+@Immutable
+public class SiblingIssue 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 branchName;
+  private final Date updateDate;
+
+  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;
+    this.lineHash = lineHash;
+    this.ruleKey = ruleKey;
+    this.status = status;
+    this.branchName = branchName;
+    this.updateDate = updateDate;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  @CheckForNull
+  @Override
+  public Integer getLine() {
+    return line;
+  }
+
+  @Override
+  public String getMessage() {
+    return message;
+  }
+
+  @CheckForNull
+  @Override
+  public String getLineHash() {
+    return lineHash;
+  }
+
+  @Override
+  public RuleKey getRuleKey() {
+    return ruleKey;
+  }
+
+  @Override
+  public String getStatus() {
+    return status;
+  }
+
+  public String getBranchName() {
+    return branchName;
+  }
+
+  @Override
+  public Date getUpdateDate() {
+    return updateDate;
+  }
+
+  @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;
+    }
+    SiblingIssue other = (SiblingIssue) obj;
+    return key.equals(other.key);
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMerger.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMerger.java
new file mode 100644 (file)
index 0000000..aa60198
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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.issue;
+
+import java.util.Collection;
+import java.util.Map;
+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;
+
+public class SiblingsIssueMerger {
+  private final SiblingsIssuesLoader siblingsIssuesLoader;
+  private final SimpleTracker<DefaultIssue, SiblingIssue> tracker;
+  private final IssueLifecycle issueLifecycle;
+
+  public SiblingsIssueMerger(SiblingsIssuesLoader resolvedSiblingsIssuesLoader, IssueLifecycle issueLifecycle) {
+    this(resolvedSiblingsIssuesLoader, new SimpleTracker<>(), issueLifecycle);
+  }
+
+  public SiblingsIssueMerger(SiblingsIssuesLoader siblingsIssuesLoader, SimpleTracker<DefaultIssue, SiblingIssue> tracker, IssueLifecycle issueLifecycle) {
+    this.siblingsIssuesLoader = siblingsIssuesLoader;
+    this.tracker = tracker;
+    this.issueLifecycle = issueLifecycle;
+  }
+
+  /**
+   * 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<SiblingIssue> siblingIssues = siblingsIssuesLoader.loadCandidateSiblingIssuesForMerging(component);
+    Tracking<DefaultIssue, SiblingIssue> tracking = tracker.track(newIssues, siblingIssues);
+
+    Map<DefaultIssue, SiblingIssue> matchedRaws = tracking.getMatchedRaws();
+
+    Map<SiblingIssue, DefaultIssue> defaultIssues = siblingsIssuesLoader.loadDefaultIssuesWithChanges(matchedRaws.values());
+
+    for (Map.Entry<DefaultIssue, SiblingIssue> e : matchedRaws.entrySet()) {
+      SiblingIssue issue = e.getValue();
+      issueLifecycle.mergeConfirmedOrResolvedFromShortLivingBranch(e.getKey(), defaultIssues.get(issue), issue.getBranchName());
+    }
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssuesLoader.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssuesLoader.java
new file mode 100644 (file)
index 0000000..1b8f2b2
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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.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.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.issue.ShortBranchIssueDto;
+
+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 SiblingsIssuesLoader {
+
+  private final SiblingComponentsWithOpenIssues siblingComponentsWithOpenIssues;
+  private final DbClient dbClient;
+  private final ComponentIssuesLoader componentIssuesLoader;
+
+  public SiblingsIssuesLoader(SiblingComponentsWithOpenIssues siblingComponentsWithOpenIssues, DbClient dbClient,
+                              ComponentIssuesLoader componentIssuesLoader) {
+    this.siblingComponentsWithOpenIssues = siblingComponentsWithOpenIssues;
+    this.dbClient = dbClient;
+    this.componentIssuesLoader = componentIssuesLoader;
+  }
+
+  public Collection<SiblingIssue> loadCandidateSiblingIssuesForMerging(Component component) {
+    String componentKey = ComponentDto.removeBranchAndPullRequestFromKey(component.getDbKey());
+    Set<String> uuids = siblingComponentsWithOpenIssues.getUuids(componentKey);
+    if (uuids.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    try (DbSession session = dbClient.openSession(false)) {
+      return dbClient.issueDao().selectOpenByComponentUuids(session, uuids)
+        .stream()
+        .map(SiblingsIssuesLoader::toSiblingIssue)
+        .collect(Collectors.toList());
+    }
+  }
+
+  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<SiblingIssue, DefaultIssue> loadDefaultIssuesWithChanges(Collection<SiblingIssue> lightIssues) {
+    if (lightIssues.isEmpty()) {
+      return Collections.emptyMap();
+    }
+
+    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()
+        .map(IssueDto::toDefaultIssue)
+        .collect(toList(issuesByKey.size()));
+      componentIssuesLoader.loadChanges(session, issues);
+      return issues.stream()
+        .collect(uniqueIndex(i -> issuesByKey.get(i.key()), i -> i, issues.size()));
+    }
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java
deleted file mode 100644 (file)
index c2d5d63..0000000
+++ /dev/null
@@ -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);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java
new file mode 100644 (file)
index 0000000..dc73d0c
--- /dev/null
@@ -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);
+  }
+}
index 5bd063b8d16c414f850d06fce339a6d383fb484e..55e56244c6294e739a02a71ace1ece6380d2f7ec 100644 (file)
@@ -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);
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssueMergerTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ShortBranchIssueMergerTest.java
deleted file mode 100644 (file)
index 6cc8c11..0000000
+++ /dev/null
@@ -1,234 +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.issue;
-
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Collections;
-import java.util.Date;
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-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.component.TreeRootHolderRule;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.issue.tracking.SimpleTracker;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.issue.IssueTesting;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.user.UserDto;
-
-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.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-
-public class ShortBranchIssueMergerTest {
-  @Mock
-  private IssueLifecycle issueLifecycle;
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
-    .setRoot(builder(org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT, PROJECT_REF).setKey(PROJECT_KEY).setUuid(PROJECT_UUID)
-      .addChildren(FILE_1)
-      .build());
-
-  private static final String PROJECT_KEY = "project";
-  private static final int PROJECT_REF = 1;
-  private static final String PROJECT_UUID = "projectUuid";
-  private static final int FILE_1_REF = 12341;
-  private static final String FILE_1_KEY = "fileKey";
-  private static final String FILE_1_UUID = "fileUuid";
-
-  private static final org.sonar.ce.task.projectanalysis.component.Component FILE_1 = builder(
-    org.sonar.ce.task.projectanalysis.component.Component.Type.FILE, FILE_1_REF)
-      .setKey(FILE_1_KEY)
-      .setUuid(FILE_1_UUID)
-      .build();
-
-  private SimpleTracker<DefaultIssue, ShortBranchIssue> tracker = new SimpleTracker<>();
-  private ShortBranchIssueMerger copier;
-  private ComponentDto fileOnBranch1Dto;
-  private ComponentDto fileOnBranch2Dto;
-  private ComponentDto fileOnBranch3Dto;
-  private ComponentDto projectDto;
-  private ComponentDto branch1Dto;
-  private ComponentDto branch2Dto;
-  private ComponentDto branch3Dto;
-  private RuleDefinitionDto rule;
-
-  @Before
-  public void setUp() {
-    MockitoAnnotations.initMocks(this);
-    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,
-      issueLifecycle);
-    projectDto = db.components().insertMainBranch(p -> p.setDbKey(PROJECT_KEY).setUuid(PROJECT_UUID));
-    branch1Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch1")
-      .setBranchType(BranchType.SHORT)
-      .setMergeBranchUuid(projectDto.uuid()));
-    branch2Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch2")
-      .setBranchType(BranchType.SHORT)
-      .setMergeBranchUuid(projectDto.uuid()));
-    branch3Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch3")
-      .setBranchType(BranchType.SHORT)
-      .setMergeBranchUuid(projectDto.uuid()));
-    fileOnBranch1Dto = db.components().insertComponent(newFileDto(branch1Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch1"));
-    fileOnBranch2Dto = db.components().insertComponent(newFileDto(branch2Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch2"));
-    fileOnBranch3Dto = db.components().insertComponent(newFileDto(branch3Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch3"));
-    rule = db.rules().insert();
-  }
-
-  @Test
-  public void do_nothing_if_no_match() {
-    DefaultIssue i = createIssue("issue1", rule.getKey(), Issue.STATUS_CONFIRMED, null, new Date());
-    copier.tryMerge(FILE_1, Collections.singleton(i));
-
-    verifyZeroInteractions(issueLifecycle);
-  }
-
-  @Test
-  public void do_nothing_if_no_new_issue() {
-    db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
-    copier.tryMerge(FILE_1, Collections.emptyList());
-
-    verifyZeroInteractions(issueLifecycle);
-  }
-
-  @Test
-  public void merge_confirmed_issues() {
-    db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
-    DefaultIssue newIssue = createIssue("issue2", 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("myBranch1"));
-
-    assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
-  }
-
-  @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() {
-    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))));
-    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))));
-    db.issues().insertIssue(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
-      .setIssueCreationDate(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"));
-
-    assertThat(issueToMerge.getValue().key()).isEqualTo("issue3");
-  }
-
-  @Test
-  public void lazy_load_changes() {
-    UserDto user1 = db.users().insertUser();
-    IssueDto issue1 = db.issues()
-      .insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum"));
-    db.issues().insertComment(issue1, user1, "A comment 1");
-    db.issues().insertFieldDiffs(issue1, FieldDiffs.parse("severity=BLOCKER|INFO,assignee=toto|titi").setCreationDate(new Date()));
-    UserDto user2 = db.users().insertUser();
-    IssueDto issue2 = db.issues()
-      .insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
-    db.issues().insertComment(issue2, user2, "A comment 2");
-    db.issues().insertFieldDiffs(issue2, FieldDiffs.parse("severity=BLOCKER|MINOR,assignee=foo|bar").setCreationDate(new Date()));
-    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("myBranch2"));
-
-    assertThat(issueToMerge.getValue().key()).isEqualTo("issue2");
-    assertThat(issueToMerge.getValue().defaultIssueComments()).isNotEmpty();
-    assertThat(issueToMerge.getValue().changes()).isNotEmpty();
-  }
-
-  private static DefaultIssue createIssue(String key, RuleKey ruleKey, String status, @Nullable String resolution, Date creationDate) {
-    DefaultIssue issue = new DefaultIssue();
-    issue.setKey(key);
-    issue.setRuleKey(ruleKey);
-    issue.setMessage("msg");
-    issue.setLine(1);
-    issue.setStatus(status);
-    issue.setResolution(resolution);
-    issue.setCreationDate(creationDate);
-    issue.setChecksum("checksum");
-    return issue;
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java
new file mode 100644 (file)
index 0000000..e595aa0
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * 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.issue;
+
+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;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+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.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;
+import org.sonar.core.issue.tracking.SimpleTracker;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.issue.IssueTesting;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.user.UserDto;
+
+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 SiblingsIssueMergerTest {
+  @Mock
+  private IssueLifecycle issueLifecycle;
+
+  @Mock
+  private Branch branch;
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
+    .setRoot(builder(org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT, PROJECT_REF).setKey(PROJECT_KEY).setUuid(PROJECT_UUID)
+      .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";
+  private static final int FILE_1_REF = 12341;
+  private static final String FILE_1_KEY = "fileKey";
+  private static final String FILE_1_UUID = "fileUuid";
+
+  private static final org.sonar.ce.task.projectanalysis.component.Component FILE_1 = builder(
+    org.sonar.ce.task.projectanalysis.component.Component.Type.FILE, FILE_1_REF)
+      .setKey(FILE_1_KEY)
+      .setUuid(FILE_1_UUID)
+      .build();
+
+  private SimpleTracker<DefaultIssue, SiblingIssue> tracker = new SimpleTracker<>();
+  private SiblingsIssueMerger copier;
+  private ComponentDto fileOnBranch1Dto;
+  private ComponentDto fileOnBranch2Dto;
+  private ComponentDto fileOnBranch3Dto;
+  private ComponentDto projectDto;
+  private ComponentDto branch1Dto;
+  private ComponentDto branch2Dto;
+  private ComponentDto branch3Dto;
+  private RuleDefinitionDto rule;
+
+  @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 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")
+      .setBranchType(BranchType.SHORT)
+      .setMergeBranchUuid(projectDto.uuid()));
+    branch2Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch2")
+      .setBranchType(BranchType.SHORT)
+      .setMergeBranchUuid(projectDto.uuid()));
+    branch3Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch3")
+      .setBranchType(BranchType.SHORT)
+      .setMergeBranchUuid(projectDto.uuid()));
+    fileOnBranch1Dto = db.components().insertComponent(newFileDto(branch1Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch1"));
+    fileOnBranch2Dto = db.components().insertComponent(newFileDto(branch2Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch2"));
+    fileOnBranch3Dto = db.components().insertComponent(newFileDto(branch3Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch3"));
+    rule = db.rules().insert();
+  }
+
+  @Test
+  public void do_nothing_if_no_match() {
+    DefaultIssue i = createIssue("issue1", rule.getKey(), Issue.STATUS_CONFIRMED, null, new Date());
+    copier.tryMerge(FILE_1, Collections.singleton(i));
+
+    verifyZeroInteractions(issueLifecycle);
+  }
+
+  @Test
+  public void do_nothing_if_no_new_issue() {
+    db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
+    copier.tryMerge(FILE_1, Collections.emptyList());
+
+    verifyZeroInteractions(issueLifecycle);
+  }
+
+  @Test
+  public void merge_confirmed_issues() {
+    db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
+    DefaultIssue newIssue = createIssue("issue2", 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("myBranch1"));
+
+    assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
+  }
+
+  @Test
+  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")
+      .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")
+      .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")
+      .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("myBranch1"));
+
+    assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
+  }
+
+  @Test
+  public void lazy_load_changes() {
+    UserDto user1 = db.users().insertUser();
+    IssueDto issue1 = db.issues()
+      .insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum"));
+    db.issues().insertComment(issue1, user1, "A comment 1");
+    db.issues().insertFieldDiffs(issue1, FieldDiffs.parse("severity=BLOCKER|INFO,assignee=toto|titi").setCreationDate(new Date()));
+    UserDto user2 = db.users().insertUser();
+    IssueDto issue2 = db.issues()
+      .insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
+    db.issues().insertComment(issue2, user2, "A comment 2");
+    db.issues().insertFieldDiffs(issue2, FieldDiffs.parse("severity=BLOCKER|MINOR,assignee=foo|bar").setCreationDate(new Date()));
+    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("myBranch2"));
+
+    assertThat(issueToMerge.getValue().key()).isEqualTo("issue2");
+    assertThat(issueToMerge.getValue().defaultIssueComments()).isNotEmpty();
+    assertThat(issueToMerge.getValue().changes()).isNotEmpty();
+  }
+
+  private static DefaultIssue createIssue(String key, RuleKey ruleKey, String status, @Nullable String resolution, Date creationDate) {
+    DefaultIssue issue = new DefaultIssue();
+    issue.setKey(key);
+    issue.setRuleKey(ruleKey);
+    issue.setMessage("msg");
+    issue.setLine(1);
+    issue.setStatus(status);
+    issue.setResolution(resolution);
+    issue.setCreationDate(creationDate);
+    issue.setChecksum("checksum");
+    return issue;
+  }
+
+}
index eca30f4dcd1eef15c8514446c16e3d38d98b4318..d065136abab6e36b72919123f1efc380aefb12a2 100644 (file)
@@ -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);
   }
 
   /**
index fac6f2c06da9353253df3e49b4890b50d74f251c..b8045d385c47666458dbe5f438366b66e84550de 100644 (file)
@@ -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);
 
index 623c790b10f282669b5883757d85f12c0fdd7d96..7876c944ab3bdc223323865a65c2b1aa4e71667d 100644 (file)
@@ -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;
   }
 
index 90ec7a99a7ec2edb414533dee2dfe33fdad1bffa..f0c7c129f0959e458f0e29c6c33f23088aa3a6ce 100644 (file)
     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>
 
index cf9958a1e5004d647c559361a2bf30e0bc83fd98..b636849ce36c5fe6a7ec0a8309b539bd8a3ed9c1 100644 (file)
       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
index 28263acb14159bfdb9a575a162f17ec881862f89..fbc50aea6bfb4a76717ebcd8748ac72f97a76ae8 100644 (file)
@@ -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
index 4bfa55b9137323ca789781b1a42eb635e127ab10..af8649794d8a29403121939bc61174363e5b0b17 100644 (file)
@@ -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;
   }
 }
index 5d1c048a797ba37464bb0eaccc625421b0770341..53473607f241e067fc6729055d5c7be6b70100dd 100644 (file)
@@ -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);
index da1891668d51bec5126faadbb05f16554436f2bf..bda8f9c606185f432729b175f6761371affa2440 100644 (file)
@@ -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();
 }
index ac612407280d8d8ad0b4ce4c176815c5742f4e1b..95a42d72db72b23e59a957faad652db9cf74bc18 100644 (file)
@@ -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;
     }
   }