]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9913 Match issues from short living branches more aggressively
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 11 Oct 2017 10:07:37 +0000 (12:07 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Fri, 20 Oct 2017 08:45:15 +0000 (18:45 +1000)
When there are multiple candidates (from multiple branches), prefer any of the RESOLVED one, before
taking any of the CONFIRMED ones.

18 files changed:
server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.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/IssueStatusCopier.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ResolvedShortBranchIssuesLoader.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueStatusCopier.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueStatusCopierTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueStatusCopierTest.java [new file with mode: 0644]
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/SimpleTracker.java
sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java
sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracker.java
sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueFromWs.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/TrackedIssue.java

index f13c016779714d3902deb997dc6525a4ff384523..0e2f88f94b9e37f602ec1317447adede659afcbd 100644 (file)
@@ -65,6 +65,7 @@ public class ShortBranchIssue implements Trackable {
     return ruleKey;
   }
 
+  @Override
   public String getStatus() {
     return status;
   }
index 267a4523e0f1757d681c0b488297e1307174d75c..2e5be26254332696c564659c9e6788624fbcc951 100644 (file)
@@ -64,7 +64,7 @@ import org.sonar.server.computation.task.projectanalysis.issue.IssueCache;
 import org.sonar.server.computation.task.projectanalysis.issue.IssueCounter;
 import org.sonar.server.computation.task.projectanalysis.issue.IssueCreationDateCalculator;
 import org.sonar.server.computation.task.projectanalysis.issue.IssueLifecycle;
-import org.sonar.server.computation.task.projectanalysis.issue.IssueStatusCopier;
+import org.sonar.server.computation.task.projectanalysis.issue.ShortBranchIssueStatusCopier;
 import org.sonar.server.computation.task.projectanalysis.issue.IssueTrackingDelegator;
 import org.sonar.server.computation.task.projectanalysis.issue.IssueVisitors;
 import org.sonar.server.computation.task.projectanalysis.issue.IssuesRepositoryVisitor;
@@ -73,7 +73,7 @@ import org.sonar.server.computation.task.projectanalysis.issue.MergeBranchTracke
 import org.sonar.server.computation.task.projectanalysis.issue.MovedIssueVisitor;
 import org.sonar.server.computation.task.projectanalysis.issue.NewEffortAggregator;
 import org.sonar.server.computation.task.projectanalysis.issue.RemoveProcessedComponentsVisitor;
-import org.sonar.server.computation.task.projectanalysis.issue.ResolvedShortBranchIssuesLoader;
+import org.sonar.server.computation.task.projectanalysis.issue.ShortBranchIssuesLoader;
 import org.sonar.server.computation.task.projectanalysis.issue.RuleRepositoryImpl;
 import org.sonar.server.computation.task.projectanalysis.issue.RuleTagsCopier;
 import org.sonar.server.computation.task.projectanalysis.issue.RuleTypeCopier;
@@ -252,8 +252,8 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
       BaseIssuesLoader.class,
       IssueTrackingDelegator.class,
       BranchPersister.class,
-      ResolvedShortBranchIssuesLoader.class,
-      IssueStatusCopier.class,
+      ShortBranchIssuesLoader.class,
+      ShortBranchIssueStatusCopier.class,
 
       // filemove
       SourceSimilarityImpl.class,
index 76cbed32dcf130e158dc2419a7bc3e47b0370c7b..26db336fcf50556a604bac3faba7064e6a402d84 100644 (file)
@@ -37,11 +37,11 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
   private final IssueLifecycle issueLifecycle;
   private final IssueVisitors issueVisitors;
   private final IssueTrackingDelegator issueTracking;
-  private final IssueStatusCopier issueStatusCopier;
+  private final ShortBranchIssueStatusCopier issueStatusCopier;
   private final AnalysisMetadataHolder analysisMetadataHolder;
 
   public IntegrateIssuesVisitor(IssueCache issueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors,
-    AnalysisMetadataHolder analysisMetadataHolder, IssueTrackingDelegator issueTracking, IssueStatusCopier issueStatusCopier) {
+    AnalysisMetadataHolder analysisMetadataHolder, IssueTrackingDelegator issueTracking, ShortBranchIssueStatusCopier issueStatusCopier) {
     super(CrawlerDepthLimit.FILE, POST_ORDER);
     this.issueCache = issueCache;
     this.issueLifecycle = issueLifecycle;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueStatusCopier.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueStatusCopier.java
deleted file mode 100644 (file)
index 40f50c1..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 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.server.computation.task.projectanalysis.issue;
-
-import java.util.Collection;
-import java.util.Map;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.ShortBranchIssue;
-import org.sonar.core.issue.tracking.SimpleTracker;
-import org.sonar.core.issue.tracking.Tracking;
-import org.sonar.server.computation.task.projectanalysis.component.Component;
-
-public class IssueStatusCopier {
-  private final ResolvedShortBranchIssuesLoader resolvedShortBranchIssuesLoader;
-  private final SimpleTracker<DefaultIssue, ShortBranchIssue> tracker;
-  private final IssueLifecycle issueLifecycle;
-
-  public IssueStatusCopier(ResolvedShortBranchIssuesLoader resolvedShortBranchIssuesLoader, IssueLifecycle issueLifecycle) {
-    this(resolvedShortBranchIssuesLoader, new SimpleTracker<>(), issueLifecycle);
-  }
-
-  public IssueStatusCopier(ResolvedShortBranchIssuesLoader resolvedShortBranchIssuesLoader, SimpleTracker<DefaultIssue, ShortBranchIssue> tracker, IssueLifecycle issueLifecycle) {
-    this.resolvedShortBranchIssuesLoader = resolvedShortBranchIssuesLoader;
-    this.tracker = tracker;
-    this.issueLifecycle = issueLifecycle;
-  }
-
-  public void updateStatus(Component component, Collection<DefaultIssue> newIssues) {
-    Collection<ShortBranchIssue> shortBranchIssues = resolvedShortBranchIssuesLoader.create(component);
-    Tracking<DefaultIssue, ShortBranchIssue> tracking = tracker.track(newIssues, shortBranchIssues);
-
-    for (Map.Entry<DefaultIssue, ShortBranchIssue> e : tracking.getMatchedRaws().entrySet()) {
-      ShortBranchIssue issue = e.getValue();
-      issueLifecycle.copyResolution(e.getKey(), issue.getStatus(), issue.getResolution());
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ResolvedShortBranchIssuesLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ResolvedShortBranchIssuesLoader.java
deleted file mode 100644 (file)
index 4e3e5c3..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 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.server.computation.task.projectanalysis.issue;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
-import java.util.stream.Collectors;
-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.ShortBranchIssueDto;
-import org.sonar.server.computation.task.projectanalysis.component.Component;
-import org.sonar.server.computation.task.projectanalysis.component.ShortBranchComponentsWithIssues;
-
-public class ResolvedShortBranchIssuesLoader {
-
-  private final ShortBranchComponentsWithIssues shortBranchComponentsWithIssues;
-  private final DbClient dbClient;
-
-  public ResolvedShortBranchIssuesLoader(ShortBranchComponentsWithIssues shortBranchComponentsWithIssues, DbClient dbClient) {
-    this.shortBranchComponentsWithIssues = shortBranchComponentsWithIssues;
-    this.dbClient = dbClient;
-  }
-
-  public Collection<ShortBranchIssue> create(Component component) {
-    String componentKey = ComponentDto.removeBranchFromKey(component.getKey());
-    Set<String> uuids = shortBranchComponentsWithIssues.getUuids(componentKey);
-    if (uuids.isEmpty()) {
-      return Collections.emptyList();
-    }
-    try (DbSession session = dbClient.openSession(false)) {
-      return dbClient.issueDao().selectResolvedOrConfirmedByComponentUuids(session, uuids)
-        .stream()
-        .map(ShortBranchIssueDto::toShortBranchIssue)
-        .collect(Collectors.toList());
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueStatusCopier.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueStatusCopier.java
new file mode 100644 (file)
index 0000000..8b49759
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.server.computation.task.projectanalysis.issue;
+
+import java.util.Collection;
+import java.util.Map;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.ShortBranchIssue;
+import org.sonar.core.issue.tracking.SimpleTracker;
+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 SimpleTracker<DefaultIssue, ShortBranchIssue> tracker;
+  private final IssueLifecycle issueLifecycle;
+
+  public ShortBranchIssueStatusCopier(ShortBranchIssuesLoader resolvedShortBranchIssuesLoader, IssueLifecycle issueLifecycle) {
+    this(resolvedShortBranchIssuesLoader, new SimpleTracker<>(), issueLifecycle);
+  }
+
+  public ShortBranchIssueStatusCopier(ShortBranchIssuesLoader resolvedShortBranchIssuesLoader, SimpleTracker<DefaultIssue, ShortBranchIssue> tracker,
+    IssueLifecycle issueLifecycle) {
+    this.resolvedShortBranchIssuesLoader = resolvedShortBranchIssuesLoader;
+    this.tracker = tracker;
+    this.issueLifecycle = issueLifecycle;
+  }
+
+  public void updateStatus(Component component, Collection<DefaultIssue> newIssues) {
+    Collection<ShortBranchIssue> shortBranchIssues = resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component);
+    Tracking<DefaultIssue, ShortBranchIssue> tracking = tracker.track(newIssues, shortBranchIssues);
+
+    for (Map.Entry<DefaultIssue, ShortBranchIssue> e : tracking.getMatchedRaws().entrySet()) {
+      ShortBranchIssue issue = e.getValue();
+      issueLifecycle.copyResolution(e.getKey(), issue.getStatus(), issue.getResolution());
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java
new file mode 100644 (file)
index 0000000..ebac0c2
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.server.computation.task.projectanalysis.issue;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+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.ShortBranchIssueDto;
+import org.sonar.server.computation.task.projectanalysis.component.Component;
+import org.sonar.server.computation.task.projectanalysis.component.ShortBranchComponentsWithIssues;
+
+public class ShortBranchIssuesLoader {
+
+  private final ShortBranchComponentsWithIssues shortBranchComponentsWithIssues;
+  private final DbClient dbClient;
+
+  public ShortBranchIssuesLoader(ShortBranchComponentsWithIssues shortBranchComponentsWithIssues, DbClient dbClient) {
+    this.shortBranchComponentsWithIssues = shortBranchComponentsWithIssues;
+    this.dbClient = dbClient;
+  }
+
+  public Collection<ShortBranchIssue> loadCandidateIssuesForMergingInTargetBranch(Component component) {
+    String componentKey = ComponentDto.removeBranchFromKey(component.getKey());
+    Set<String> uuids = shortBranchComponentsWithIssues.getUuids(componentKey);
+    if (uuids.isEmpty()) {
+      return Collections.emptyList();
+    }
+    try (DbSession session = dbClient.openSession(false)) {
+      return dbClient.issueDao().selectResolvedOrConfirmedByComponentUuids(session, uuids)
+        .stream()
+        .map(ShortBranchIssueDto::toShortBranchIssue)
+        .collect(Collectors.toList());
+    }
+  }
+}
index 6dd63fa7f24d6b0554bc545524a9af615bd7b529..8f30a43833326e3f8e5a4c69371f4a79d077f3a6 100644 (file)
@@ -113,7 +113,7 @@ public class IntegrateIssuesVisitorTest {
   @Mock
   private MergeBranchComponentUuids mergeBranchComponentsUuids;
   @Mock
-  private IssueStatusCopier issueStatusCopier;
+  private ShortBranchIssueStatusCopier issueStatusCopier;
 
   ArgumentCaptor<DefaultIssue> defaultIssueCaptor;
 
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueStatusCopierTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueStatusCopierTest.java
deleted file mode 100644 (file)
index 9716ed4..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 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.server.computation.task.projectanalysis.issue;
-
-import java.util.Collections;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.DefaultIssue;
-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.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-public class IssueStatusCopierTest {
-  @Mock
-  private ResolvedShortBranchIssuesLoader resolvedShortBranchIssuesLoader;
-  @Mock
-  private IssueLifecycle issueLifecycle;
-  @Mock
-  private Component component;
-
-  private SimpleTracker<DefaultIssue, ShortBranchIssue> tracker = new SimpleTracker<>();
-  private IssueStatusCopier copier;
-
-  @Before
-  public void setUp() {
-    MockitoAnnotations.initMocks(this);
-    copier = new IssueStatusCopier(resolvedShortBranchIssuesLoader, tracker, issueLifecycle);
-  }
-
-  @Test
-  public void do_nothing_if_no_match() {
-    when(resolvedShortBranchIssuesLoader.create(component)).thenReturn(Collections.emptyList());
-    DefaultIssue i = createIssue("issue1", "rule1");
-    copier.updateStatus(component, Collections.singleton(i));
-
-    verify(resolvedShortBranchIssuesLoader).create(component);
-    verifyZeroInteractions(issueLifecycle);
-  }
-
-  @Test
-  public void do_nothing_if_no_new_issue() {
-    DefaultIssue i = createIssue("issue1", "rule1");
-    when(resolvedShortBranchIssuesLoader.create(component)).thenReturn(Collections.singleton(newShortBranchIssue(i)));
-    copier.updateStatus(component, Collections.emptyList());
-
-    verify(resolvedShortBranchIssuesLoader).create(component);
-    verifyZeroInteractions(issueLifecycle);
-  }
-
-  @Test
-  public void update_status_on_matches() {
-    ShortBranchIssue shortBranchIssue = newShortBranchIssue(createIssue("issue1", "rule1"));
-    DefaultIssue newIssue = createIssue("issue2", "rule1");
-
-    when(resolvedShortBranchIssuesLoader.create(component)).thenReturn(Collections.singleton(shortBranchIssue));
-    copier.updateStatus(component, Collections.singleton(newIssue));
-    verify(issueLifecycle).copyResolution(newIssue, shortBranchIssue.getStatus(), shortBranchIssue.getResolution());
-  }
-
-  private static DefaultIssue createIssue(String key, String ruleKey) {
-    DefaultIssue issue = new DefaultIssue();
-    issue.setKey(key);
-    issue.setRuleKey(RuleKey.of("repo", ruleKey));
-    issue.setMessage("msg");
-    issue.setLine(1);
-    return issue;
-  }
-
-  private ShortBranchIssue newShortBranchIssue(DefaultIssue i) {
-    return new ShortBranchIssue(i.line(), i.message(), i.getLineHash(), i.ruleKey(), i.status(), i.resolution());
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueStatusCopierTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueStatusCopierTest.java
new file mode 100644 (file)
index 0000000..c5948cf
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.server.computation.task.projectanalysis.issue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.DefaultIssue;
+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.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class ShortBranchIssueStatusCopierTest {
+  @Mock
+  private ShortBranchIssuesLoader resolvedShortBranchIssuesLoader;
+  @Mock
+  private IssueLifecycle issueLifecycle;
+  @Mock
+  private Component component;
+
+  private SimpleTracker<DefaultIssue, ShortBranchIssue> tracker = new SimpleTracker<>();
+  private ShortBranchIssueStatusCopier copier;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    copier = new ShortBranchIssueStatusCopier(resolvedShortBranchIssuesLoader, tracker, issueLifecycle);
+  }
+
+  @Test
+  public void do_nothing_if_no_match() {
+    when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.emptyList());
+    DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null);
+    copier.updateStatus(component, Collections.singleton(i));
+
+    verify(resolvedShortBranchIssuesLoader).loadCandidateIssuesForMergingInTargetBranch(component);
+    verifyZeroInteractions(issueLifecycle);
+  }
+
+  @Test
+  public void do_nothing_if_no_new_issue() {
+    DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null);
+    when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.singleton(newShortBranchIssue(i)));
+    copier.updateStatus(component, Collections.emptyList());
+
+    verify(resolvedShortBranchIssuesLoader).loadCandidateIssuesForMergingInTargetBranch(component);
+    verifyZeroInteractions(issueLifecycle);
+  }
+
+  @Test
+  public void update_status_on_matches() {
+    ShortBranchIssue shortBranchIssue = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null));
+    DefaultIssue newIssue = createIssue("issue2", "rule1", Issue.STATUS_OPEN, null);
+
+    when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.singleton(shortBranchIssue));
+    copier.updateStatus(component, Collections.singleton(newIssue));
+    verify(issueLifecycle).copyResolution(newIssue, shortBranchIssue.getStatus(), shortBranchIssue.getResolution());
+  }
+
+  @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 newIssue = createIssue("newIssue", "rule1", Issue.STATUS_OPEN, null);
+
+    when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Arrays.asList(shortBranchIssue1, shortBranchIssue2, shortBranchIssue3));
+    copier.updateStatus(component, Collections.singleton(newIssue));
+    verify(issueLifecycle).copyResolution(newIssue, Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE);
+  }
+
+  private static DefaultIssue createIssue(String key, String ruleKey, String status, @Nullable String resolution) {
+    DefaultIssue issue = new DefaultIssue();
+    issue.setKey(key);
+    issue.setRuleKey(RuleKey.of("repo", ruleKey));
+    issue.setMessage("msg");
+    issue.setLine(1);
+    issue.setStatus(status);
+    issue.setResolution(resolution);
+    return issue;
+  }
+
+  private ShortBranchIssue newShortBranchIssue(DefaultIssue i) {
+    return new ShortBranchIssue(i.line(), i.message(), i.getLineHash(), i.ruleKey(), i.status(), i.resolution());
+  }
+}
index 198515da6d05355d9bd806865b9305b279287ce8..fd729489a70e7c1bc9eb17ca64e0067450d883a1 100644 (file)
@@ -648,4 +648,9 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
   public RuleKey getRuleKey() {
     return ruleKey;
   }
+
+  @Override
+  public String getStatus() {
+    return status;
+  }
 }
index bdf7dd75d5dedec8600b1c4bf013edb03b410c98..690969933245b01993e549c94d1340d26425afed 100644 (file)
@@ -22,19 +22,20 @@ package org.sonar.core.issue.tracking;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
 import java.util.Collection;
-import java.util.Iterator;
 import java.util.Objects;
+import java.util.function.Function;
 import javax.annotation.Nonnull;
 import org.apache.commons.lang.StringUtils;
+import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.RuleKey;
 
 public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
 
-  protected void match(Tracking<RAW, BASE> tracking, SearchKeyFactory factory) {
-    match(tracking, factory, false);
+  protected void match(Tracking<RAW, BASE> tracking, Function<Trackable, SearchKey> searchKeyFactory) {
+    match(tracking, searchKeyFactory, false);
   }
 
-  protected void match(Tracking<RAW, BASE> tracking, SearchKeyFactory factory, boolean rejectMultipleMatches) {
+  protected void match(Tracking<RAW, BASE> tracking, Function<Trackable, SearchKey> searchKeyFactory, boolean preferResolved) {
 
     if (tracking.isComplete()) {
       return;
@@ -42,39 +43,39 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
 
     Multimap<SearchKey, BASE> baseSearch = ArrayListMultimap.create();
     for (BASE base : tracking.getUnmatchedBases()) {
-      baseSearch.put(factory.create(base), base);
+      baseSearch.put(searchKeyFactory.apply(base), base);
     }
 
     for (RAW raw : tracking.getUnmatchedRaws()) {
-      SearchKey rawKey = factory.create(raw);
+      SearchKey rawKey = searchKeyFactory.apply(raw);
       Collection<BASE> bases = baseSearch.get(rawKey);
       if (!bases.isEmpty()) {
-        Iterator<BASE> it = bases.iterator();
-        BASE match = it.next();
-        if (rejectMultipleMatches && it.hasNext()) {
-          continue;
+        BASE match;
+        if (preferResolved) {
+          match = bases.stream()
+            .filter(i -> Issue.STATUS_RESOLVED.equals(i.getStatus()))
+            .findFirst()
+            .orElse(bases.iterator().next());
+        } else {
+          // TODO taking the first one. Could be improved if there are more than 2 issues on the same line.
+          // Message could be checked to take the best one.
+          match = bases.iterator().next();
         }
-        // TODO taking the first one. Could be improved if there are more than 2 issues on the same line.
-        // Message could be checked to take the best one.
         tracking.match(raw, match);
         baseSearch.remove(rawKey, match);
       }
     }
   }
 
-  private interface SearchKey {
+  protected interface SearchKey {
   }
 
-  private interface SearchKeyFactory {
-    SearchKey create(Trackable trackable);
-  }
-
-  private static class LineAndLineHashKey implements SearchKey {
+  protected static class LineAndLineHashKey implements SearchKey {
     private final RuleKey ruleKey;
     private final String lineHash;
     private final Integer line;
 
-    LineAndLineHashKey(Trackable trackable) {
+    protected LineAndLineHashKey(Trackable trackable) {
       this.ruleKey = trackable.getRuleKey();
       this.line = trackable.getLine();
       this.lineHash = StringUtils.defaultString(trackable.getLineHash(), "");
@@ -98,15 +99,7 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
     }
   }
 
-  protected enum LineAndLineHashKeyFactory implements SearchKeyFactory {
-    INSTANCE;
-    @Override
-    public SearchKey create(Trackable t) {
-      return new LineAndLineHashKey(t);
-    }
-  }
-
-  private static class LineHashAndMessageKey implements SearchKey {
+  protected static class LineHashAndMessageKey implements SearchKey {
     private final RuleKey ruleKey;
     private final String message;
     private final String lineHash;
@@ -135,15 +128,7 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
     }
   }
 
-  protected enum LineHashAndMessageKeyFactory implements SearchKeyFactory {
-    INSTANCE;
-    @Override
-    public SearchKey create(Trackable t) {
-      return new LineHashAndMessageKey(t);
-    }
-  }
-
-  private static class LineAndMessageKey implements SearchKey {
+  protected static class LineAndMessageKey implements SearchKey {
     private final RuleKey ruleKey;
     private final String message;
     private final Integer line;
@@ -172,15 +157,7 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
     }
   }
 
-  protected enum LineAndMessageKeyFactory implements SearchKeyFactory {
-    INSTANCE;
-    @Override
-    public SearchKey create(Trackable t) {
-      return new LineAndMessageKey(t);
-    }
-  }
-
-  private static class LineHashKey implements SearchKey {
+  protected static class LineHashKey implements SearchKey {
     private final RuleKey ruleKey;
     private final String lineHash;
 
@@ -206,11 +183,4 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
     }
   }
 
-  protected enum LineHashKeyFactory implements SearchKeyFactory {
-    INSTANCE;
-    @Override
-    public SearchKey create(Trackable t) {
-      return new LineHashKey(t);
-    }
-  }
 }
index 4ae1833c8f47dcfe2aaa0fa2204761b2c7ae3713..54fd8ea2bba5a915290f6b46720be367333dc719 100644 (file)
@@ -31,10 +31,10 @@ public class SimpleTracker<RAW extends Trackable, BASE extends Trackable> extend
     Tracking<RAW, BASE> tracking = new Tracking<>(rawInput, baseInput);
 
     // 1. match issues with same rule, same line and same line hash, but not necessarily with same message
-    match(tracking, LineAndLineHashKeyFactory.INSTANCE, true);
+    match(tracking, LineAndLineHashKey::new, true);
 
     // 2. match issues with same rule, same message and same line hash
-    match(tracking, LineHashAndMessageKeyFactory.INSTANCE, true);
+    match(tracking, LineHashAndMessageKey::new, true);
 
     return tracking;
   }
index c723ef06f27352883b24354bed24aa5190649181..0d04b81e36682efabc7fbbc9cf32d902cc73a3fe 100644 (file)
@@ -40,4 +40,6 @@ public interface Trackable {
   String getLineHash();
 
   RuleKey getRuleKey();
+
+  String getStatus();
 }
index 2039219ff89df8c0b63ac7a70fba6471e021bd91..dcbbb869ac15f9cc87babf1dfeada309738e5864 100644 (file)
@@ -19,8 +19,8 @@
  */
 package org.sonar.core.issue.tracking;
 
-import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.batch.ScannerSide;
 
 @InstantiationStrategy(InstantiationStrategy.PER_BATCH)
 @ScannerSide
@@ -30,20 +30,20 @@ public class Tracker<RAW extends Trackable, BASE extends Trackable> extends Abst
     Tracking<RAW, BASE> tracking = new Tracking<>(rawInput.getIssues(), baseInput.getIssues());
 
     // 1. match issues with same rule, same line and same line hash, but not necessarily with same message
-    match(tracking, LineAndLineHashKeyFactory.INSTANCE);
+    match(tracking, LineAndLineHashKey::new);
 
     // 2. detect code moves by comparing blocks of codes
     detectCodeMoves(rawInput, baseInput, tracking);
 
     // 3. match issues with same rule, same message and same line hash
-    match(tracking, LineHashAndMessageKeyFactory.INSTANCE);
+    match(tracking, LineHashAndMessageKey::new);
 
     // 4. match issues with same rule, same line and same message
-    match(tracking, LineAndMessageKeyFactory.INSTANCE);
+    match(tracking, LineAndMessageKey::new);
 
     // 5. match issues with same rule and same line hash but different line and different message.
     // See SONAR-2812
-    match(tracking, LineHashKeyFactory.INSTANCE);
+    match(tracking, LineHashKey::new);
 
     return tracking;
   }
index 3f5b28cdba4eb725e361a1167829b145b2ad3609..59e106f1433dcbcbbaecb8447185887297d7d1d8 100644 (file)
@@ -186,7 +186,7 @@ public class TrackerTest {
   @Test
   public void do_not_fail_if_raw_line_does_not_exist() {
     FakeInput baseInput = new FakeInput();
-    FakeInput rawInput = new FakeInput("H1").addIssue(new Issue(200, "H200", RULE_SYSTEM_PRINT, "msg"));
+    FakeInput rawInput = new FakeInput("H1").addIssue(new Issue(200, "H200", RULE_SYSTEM_PRINT, "msg", org.sonar.api.issue.Issue.STATUS_OPEN));
 
     Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput);
 
@@ -385,11 +385,13 @@ public class TrackerTest {
     private final RuleKey ruleKey;
     private final Integer line;
     private final String message, lineHash;
+    private final String status;
 
-    Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message) {
+    Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status) {
       this.line = line;
       this.lineHash = lineHash;
       this.ruleKey = ruleKey;
+      this.status = status;
       this.message = trim(message);
     }
 
@@ -412,6 +414,11 @@ public class TrackerTest {
     public RuleKey getRuleKey() {
       return ruleKey;
     }
+
+    @Override
+    public String getStatus() {
+      return status;
+    }
   }
 
   private static class FakeInput implements Input<Issue> {
@@ -431,7 +438,7 @@ public class TrackerTest {
     }
 
     Issue createIssueOnLine(int line, RuleKey ruleKey, String message) {
-      Issue issue = new Issue(line, lineHashes.get(line - 1), ruleKey, message);
+      Issue issue = new Issue(line, lineHashes.get(line - 1), ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN);
       issues.add(issue);
       return issue;
     }
@@ -440,7 +447,7 @@ public class TrackerTest {
      * No line (line 0)
      */
     Issue createIssue(RuleKey ruleKey, String message) {
-      Issue issue = new Issue(null, "", ruleKey, message);
+      Issue issue = new Issue(null, "", ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN);
       issues.add(issue);
       return issue;
     }
index 914f8af2c88ac9551fb54be1d553bfd3dc0dfff8..a0888d20341fe7842341e4ec75503aacf06cc838 100644 (file)
@@ -63,4 +63,9 @@ public class ServerIssueFromWs implements Trackable {
     return dto.hasMsg() ? trim(dto.getMsg()) : "";
   }
 
+  @Override
+  public String getStatus() {
+    throw new UnsupportedOperationException();
+  }
+
 }
index d1b689640930315dbc111c98c3c7028d81797880..aae406f92a0c0d1f1de39da13b501d82fc699069 100644 (file)
@@ -180,6 +180,11 @@ public class TrackedIssue implements Trackable, Serializable {
     return ruleKey;
   }
 
+  @Override
+  public String getStatus() {
+    return status;
+  }
+
   public String severity() {
     return severity;
   }