aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDuarte Meneses <duarte.meneses@sonarsource.com>2017-08-16 16:03:25 +0200
committerJanos Gyerik <janos.gyerik@sonarsource.com>2017-09-12 10:59:56 +0200
commit6affc4080d8b98d499174f64e2c86c84cbd2872d (patch)
tree2c737e253ee9db89d53fee73e31f2f5896ad41e9
parentfe946b35cfad0e572dc07408e6ac1fabc9f65958 (diff)
downloadsonarqube-6affc4080d8b98d499174f64e2c86c84cbd2872d.tar.gz
sonarqube-6affc4080d8b98d499174f64e2c86c84cbd2872d.zip
SONAR-9720 Issue tracking for long branches
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitor.java26
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycle.java23
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java37
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/MergeBranchTrackerExecution.java42
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchTrackerExecution.java8
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/TrackingResult.java55
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStep.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java13
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueAssignerTest.java9
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycleTest.java61
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegatorTest.java34
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/MergeBranchTrackerExecutionTest.java65
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStepTest.java35
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java17
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java1
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java2
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java6
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueAdapterForFilter.java5
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueWrapper.java5
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/TrackedIssueAdapter.java5
21 files changed, 419 insertions, 35 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
index 414920d19a3..f2be5a852fa 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
@@ -67,6 +67,7 @@ import org.sonar.server.computation.task.projectanalysis.issue.IssueTrackingDele
import org.sonar.server.computation.task.projectanalysis.issue.IssueVisitors;
import org.sonar.server.computation.task.projectanalysis.issue.IssuesRepositoryVisitor;
import org.sonar.server.computation.task.projectanalysis.issue.LoadComponentUuidsHavingOpenIssuesVisitor;
+import org.sonar.server.computation.task.projectanalysis.issue.MergeBranchTrackerExecution;
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.NewEffortCalculator;
@@ -239,6 +240,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
Tracker.class,
TrackerExecution.class,
ShortBranchTrackerExecution.class,
+ MergeBranchTrackerExecution.class,
ComponentIssuesLoader.class,
BaseIssuesLoader.class,
IssueTrackingDelegator.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitor.java
index 528372d82e3..3a282e0663c 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitor.java
@@ -26,7 +26,6 @@ import java.util.List;
import java.util.Map;
import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.tracking.Tracking;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.Component.Status;
@@ -60,13 +59,15 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
issueVisitors.beforeComponent(component);
if (isIncremental(component)) {
+ // no tracking needed, simply re-use existing issues
List<DefaultIssue> issues = issuesLoader.loadForComponentUuid(component.getUuid());
- fillIncrementalOpenIssues(component, issues, cacheAppender);
+ reuseOpenIssues(component, issues, cacheAppender);
} else {
- Tracking<DefaultIssue, DefaultIssue> tracking = issueTracking.track(component);
- fillNewOpenIssues(component, tracking.getUnmatchedRaws(), cacheAppender);
- fillExistingOpenIssues(component, tracking.getMatchedRaws(), cacheAppender);
- closeUnmatchedBaseIssues(component, tracking.getUnmatchedBases(), cacheAppender);
+ TrackingResult tracking = issueTracking.track(component);
+ fillNewOpenIssues(component, tracking.newIssues(), cacheAppender);
+ fillExistingOpenIssues(component, tracking.issuesToMerge(), cacheAppender);
+ closeIssues(component, tracking.issuesToClose(), cacheAppender);
+ copyIssues(component, tracking.issuesToCopy(), cacheAppender);
}
issueVisitors.afterComponent(component);
} catch (Exception e) {
@@ -85,7 +86,16 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
}
}
- private void fillIncrementalOpenIssues(Component component, Collection<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ private void copyIssues(Component component, Map<DefaultIssue, DefaultIssue> matched, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ for (Map.Entry<DefaultIssue, DefaultIssue> entry : matched.entrySet()) {
+ DefaultIssue raw = entry.getKey();
+ DefaultIssue base = entry.getValue();
+ issueLifecycle.copyExistingOpenIssue(raw, base);
+ process(component, raw, cacheAppender);
+ }
+ }
+
+ private void reuseOpenIssues(Component component, Collection<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
for (DefaultIssue issue : issues) {
process(component, issue, cacheAppender);
}
@@ -100,7 +110,7 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
}
}
- private void closeUnmatchedBaseIssues(Component component, Iterable<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ private void closeIssues(Component component, Iterable<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
for (DefaultIssue issue : issues) {
// TODO should replace flag "beingClosed" by express call to transition "automaticClose"
issue.setBeingClosed(true);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycle.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycle.java
index 6ee0a12e76c..15c8e0017c9 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycle.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycle.java
@@ -65,6 +65,29 @@ public class IssueLifecycle {
issue.setEffort(debtCalculator.calculate(issue));
}
+ public void copyExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
+ raw.setKey(Uuids.create());
+ raw.setNew(false);
+ raw.setCopied(true);
+ raw.setType(base.type());
+ raw.setCreationDate(base.creationDate());
+ raw.setUpdateDate(base.updateDate());
+ raw.setCloseDate(base.closeDate());
+ raw.setResolution(base.resolution());
+ raw.setStatus(base.status());
+ raw.setAssignee(base.assignee());
+ raw.setAuthorLogin(base.authorLogin());
+ raw.setTags(base.tags());
+ raw.setAttributes(base.attributes());
+ raw.setEffort(debtCalculator.calculate(raw));
+ raw.setOnDisabledRule(base.isOnDisabledRule());
+ if (base.manualSeverity()) {
+ raw.setManualSeverity(true);
+ raw.setSeverity(base.severity());
+ }
+ raw.setSelectedAt(base.selectedAt());
+ }
+
public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
raw.setNew(false);
raw.setKey(base.key());
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java
index c8ce3631284..655b482c58d 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java
@@ -19,28 +19,57 @@
*/
package org.sonar.server.computation.task.projectanalysis.issue;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
+
+import java.util.Optional;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.db.component.BranchType;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
import org.sonar.server.computation.task.projectanalysis.component.Component;
public class IssueTrackingDelegator {
private final ShortBranchTrackerExecution shortBranchTracker;
private final TrackerExecution tracker;
private final AnalysisMetadataHolder analysisMetadataHolder;
+ private final MergeBranchTrackerExecution mergeBranchTracker;
- public IssueTrackingDelegator(ShortBranchTrackerExecution shortBranchTracker, TrackerExecution tracker, AnalysisMetadataHolder analysisMetadataHolder) {
+ public IssueTrackingDelegator(ShortBranchTrackerExecution shortBranchTracker, MergeBranchTrackerExecution longBranchTracker,
+ TrackerExecution tracker, AnalysisMetadataHolder analysisMetadataHolder) {
this.shortBranchTracker = shortBranchTracker;
+ this.mergeBranchTracker = longBranchTracker;
this.tracker = tracker;
this.analysisMetadataHolder = analysisMetadataHolder;
}
- public Tracking<DefaultIssue, DefaultIssue> track(Component component) {
+ public TrackingResult track(Component component) {
if (analysisMetadataHolder.isShortLivingBranch()) {
- return shortBranchTracker.track(component);
+ return standardResult(shortBranchTracker.track(component));
+ } else if (isFirstAnalysisSecondaryLongLivingBranch()) {
+ Tracking<DefaultIssue, DefaultIssue> tracking = mergeBranchTracker.track(component);
+ return new TrackingResult(tracking.getMatchedRaws(), emptyMap(), emptyList(), tracking.getUnmatchedRaws());
} else {
- return tracker.track(component);
+ return standardResult(tracker.track(component));
+ }
+ }
+
+ private static TrackingResult standardResult(Tracking<DefaultIssue, DefaultIssue> tracking) {
+ return new TrackingResult(emptyMap(), tracking.getMatchedRaws(), tracking.getUnmatchedBases(), tracking.getUnmatchedRaws());
+ }
+
+ /**
+ * Special case where we want to do the issue tracking with the merge branch, and copy matched issue to the current branch.
+ */
+ private boolean isFirstAnalysisSecondaryLongLivingBranch() {
+ if (analysisMetadataHolder.isFirstAnalysis()) {
+ Optional<Branch> branch = analysisMetadataHolder.getBranch();
+ if (branch.isPresent()) {
+ return !branch.get().isMain() && branch.get().getType() == BranchType.LONG;
+ }
}
+ return false;
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/MergeBranchTrackerExecution.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/MergeBranchTrackerExecution.java
new file mode 100644
index 00000000000..ed84ffd1e2d
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/MergeBranchTrackerExecution.java
@@ -0,0 +1,42 @@
+/*
+ * 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 org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Tracker;
+import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.server.computation.task.projectanalysis.component.Component;
+
+public class MergeBranchTrackerExecution {
+ private final TrackerRawInputFactory rawInputFactory;
+ private final TrackerMergeBranchInputFactory mergeInputFactory;
+ private final Tracker<DefaultIssue, DefaultIssue> tracker;
+
+ public MergeBranchTrackerExecution(TrackerRawInputFactory rawInputFactory, TrackerMergeBranchInputFactory mergeInputFactory,
+ Tracker<DefaultIssue, DefaultIssue> tracker) {
+ this.rawInputFactory = rawInputFactory;
+ this.mergeInputFactory = mergeInputFactory;
+ this.tracker = tracker;
+ }
+
+ public Tracking<DefaultIssue, DefaultIssue> track(Component component) {
+ return tracker.track(rawInputFactory.create(component), mergeInputFactory.create(component));
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchTrackerExecution.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchTrackerExecution.java
index f5888d5c2cb..ebc4d88b332 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchTrackerExecution.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchTrackerExecution.java
@@ -29,10 +29,10 @@ import org.sonar.core.issue.tracking.Tracking;
import org.sonar.server.computation.task.projectanalysis.component.Component;
public class ShortBranchTrackerExecution {
- private TrackerBaseInputFactory baseInputFactory;
- private TrackerRawInputFactory rawInputFactory;
- private TrackerMergeBranchInputFactory mergeInputFactory;
- private Tracker<DefaultIssue, DefaultIssue> tracker;
+ private final TrackerBaseInputFactory baseInputFactory;
+ private final TrackerRawInputFactory rawInputFactory;
+ private final TrackerMergeBranchInputFactory mergeInputFactory;
+ private final Tracker<DefaultIssue, DefaultIssue> tracker;
public ShortBranchTrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory, TrackerMergeBranchInputFactory mergeInputFactory,
Tracker<DefaultIssue, DefaultIssue> tracker) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/TrackingResult.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/TrackingResult.java
new file mode 100644
index 00000000000..b469301b4bd
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/TrackingResult.java
@@ -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.Map;
+
+import org.sonar.core.issue.DefaultIssue;
+
+public class TrackingResult {
+ private final Map<DefaultIssue, DefaultIssue> issuesToCopy;
+ private final Map<DefaultIssue, DefaultIssue> issuesToMerge;
+ private final Iterable<DefaultIssue> issuesToClose;
+ private final Iterable<DefaultIssue> newIssues;
+
+ public TrackingResult(Map<DefaultIssue, DefaultIssue> issuesToCopy, Map<DefaultIssue, DefaultIssue> issuesToMerge,
+ Iterable<DefaultIssue> issuesToClose, Iterable<DefaultIssue> newIssues) {
+ this.issuesToCopy = issuesToCopy;
+ this.issuesToMerge = issuesToMerge;
+ this.issuesToClose = issuesToClose;
+ this.newIssues = newIssues;
+ }
+
+ public Map<DefaultIssue, DefaultIssue> issuesToCopy() {
+ return issuesToCopy;
+ }
+
+ public Map<DefaultIssue, DefaultIssue> issuesToMerge() {
+ return issuesToMerge;
+ }
+
+ public Iterable<DefaultIssue> issuesToClose() {
+ return issuesToClose;
+ }
+
+ public Iterable<DefaultIssue> newIssues() {
+ return newIssues;
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStep.java
index 25ee9915bf5..6276bf44b25 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStep.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStep.java
@@ -73,10 +73,11 @@ public class PersistIssuesStep implements ComputationStep {
}
private boolean persistIssueIfRequired(IssueMapper mapper, DefaultIssue issue) {
- if (issue.isNew()) {
+ if (issue.isNew() || issue.isCopied()) {
persistNewIssue(mapper, issue);
return true;
}
+
if (issue.isChanged()) {
persistChangedIssue(mapper, issue);
return true;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
index 6c663d28250..0ae5dada91c 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
@@ -119,6 +119,7 @@ public class IntegrateIssuesVisitorTest {
IssueTrackingDelegator trackingDelegator;
TrackerExecution tracker;
ShortBranchTrackerExecution shortBranchTracker;
+ MergeBranchTrackerExecution mergeBranchTracker;
IssueCache issueCache;
TypeAwareVisitor underTest;
@@ -135,8 +136,9 @@ public class IntegrateIssuesVisitorTest {
TrackerMergeBranchInputFactory mergeInputFactory = new TrackerMergeBranchInputFactory(issuesLoader, analysisMetadataHolder, dbTester.getDbClient());
tracker = new TrackerExecution(baseInputFactory, rawInputFactory, new Tracker<>());
shortBranchTracker = new ShortBranchTrackerExecution(baseInputFactory, rawInputFactory, mergeInputFactory, new Tracker<>());
+ mergeBranchTracker = new MergeBranchTrackerExecution(rawInputFactory, mergeInputFactory, new Tracker<>());
- trackingDelegator = new IssueTrackingDelegator(shortBranchTracker, tracker, analysisMetadataHolder);
+ trackingDelegator = new IssueTrackingDelegator(shortBranchTracker, mergeBranchTracker, tracker, analysisMetadataHolder);
treeRootHolder.setRoot(PROJECT);
issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
when(analysisMetadataHolder.isIncrementalAnalysis()).thenReturn(false);
@@ -166,11 +168,6 @@ public class IntegrateIssuesVisitorTest {
}
@Test
- public void process_short_branch_issues() {
- //TODO
- }
-
- @Test
public void process_new_issue() throws Exception {
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setMsg("the message")
@@ -268,10 +265,6 @@ public class IntegrateIssuesVisitorTest {
underTest.visitAny(FILE);
}
- private void addMergeIssue(RuleKey ruleKey) {
-
- }
-
private void addBaseIssue(RuleKey ruleKey) {
ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), PROJECT_UUID).setDbKey(PROJECT_KEY);
ComponentDto file = ComponentTesting.newFileDto(project, null, FILE_UUID).setDbKey(FILE_KEY);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueAssignerTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueAssignerTest.java
index a82ece85b5a..7780f069ae2 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueAssignerTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueAssignerTest.java
@@ -20,6 +20,7 @@
package org.sonar.server.computation.task.projectanalysis.issue;
import org.junit.Test;
+import org.junit.Rule;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
@@ -40,13 +41,13 @@ public class IssueAssignerTest {
static final int FILE_REF = 1;
static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build();
- @org.junit.Rule
+ @Rule
public LogTester logTester = new LogTester();
- @org.junit.Rule
+ @Rule
public ScmInfoRepositoryRule scmInfoRepository = new ScmInfoRepositoryRule();
- @org.junit.Rule
+ @Rule
public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(123456789L);
ScmAccountToUser scmAccountToUser = mock(ScmAccountToUser.class);
@@ -185,7 +186,7 @@ public class IssueAssignerTest {
"moduleUuid=<null>,moduleUuidPath=<null>,projectUuid=<null>,projectKey=<null>,ruleKey=<null>,language=<null>,severity=<null>," +
"manualSeverity=false,message=<null>,line=2,gap=<null>,effort=<null>,status=<null>,resolution=<null>," +
"assignee=<null>,checksum=<null>,attributes=<null>,authorLogin=<null>,comments=<null>,tags=<null>," +
- "locations=<null>,creationDate=<null>,updateDate=<null>,closeDate=<null>,currentChange=<null>,changes=<null>,isNew=true," +
+ "locations=<null>,creationDate=<null>,updateDate=<null>,closeDate=<null>,currentChange=<null>,changes=<null>,isNew=true,isCopied=false," +
"beingClosed=false,onDisabledRule=false,isChanged=false,sendNotifications=false,selectedAt=<null>]");
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycleTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycleTest.java
index a056ccecdde..f7e3a07c7b1 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycleTest.java
@@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.STATUS_CLOSED;
@@ -70,6 +71,66 @@ public class IssueLifecycleTest {
assertThat(issue.updateDate()).isNotNull();
assertThat(issue.status()).isEqualTo(STATUS_OPEN);
assertThat(issue.debt()).isEqualTo(DEFAULT_DURATION);
+ assertThat(issue.isNew()).isTrue();
+ assertThat(issue.isCopied()).isFalse();
+ }
+
+ @Test
+ public void copiedIssue() throws Exception {
+ DefaultIssue raw = new DefaultIssue()
+ .setNew(true)
+ .setKey("RAW_KEY")
+ .setCreationDate(parseDate("2015-10-01"))
+ .setUpdateDate(parseDate("2015-10-02"))
+ .setCloseDate(parseDate("2015-10-03"));
+
+ DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder()
+ .setTextRange(DbCommons.TextRange.newBuilder()
+ .setStartLine(10)
+ .setEndLine(12)
+ .build())
+ .build();
+ DefaultIssue base = new DefaultIssue()
+ .setKey("BASE_KEY")
+ .setCreationDate(parseDate("2015-01-01"))
+ .setUpdateDate(parseDate("2015-01-02"))
+ .setCloseDate(parseDate("2015-01-03"))
+ .setResolution(RESOLUTION_FIXED)
+ .setStatus(STATUS_CLOSED)
+ .setSeverity(BLOCKER)
+ .setAssignee("base assignee")
+ .setAuthorLogin("base author")
+ .setTags(newArrayList("base tag"))
+ .setOnDisabledRule(true)
+ .setSelectedAt(1000L)
+ .setLine(10)
+ .setMessage("message")
+ .setGap(15d)
+ .setEffort(Duration.create(15L))
+ .setManualSeverity(false)
+ .setLocations(issueLocations);
+
+ when(debtCalculator.calculate(raw)).thenReturn(DEFAULT_DURATION);
+
+ underTest.copyExistingOpenIssue(raw, base);
+
+ assertThat(raw.isNew()).isFalse();
+ assertThat(raw.isCopied()).isTrue();
+ assertThat(raw.key()).isNotNull();
+ assertThat(raw.key()).isNotEqualTo(base.key());
+ assertThat(raw.creationDate()).isEqualTo(base.creationDate());
+ assertThat(raw.updateDate()).isEqualTo(base.updateDate());
+ assertThat(raw.closeDate()).isEqualTo(base.closeDate());
+ assertThat(raw.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(raw.status()).isEqualTo(STATUS_CLOSED);
+ assertThat(raw.assignee()).isEqualTo("base assignee");
+ assertThat(raw.authorLogin()).isEqualTo("base author");
+ assertThat(raw.tags()).containsOnly("base tag");
+ assertThat(raw.debt()).isEqualTo(DEFAULT_DURATION);
+ assertThat(raw.isOnDisabledRule()).isTrue();
+ assertThat(raw.selectedAt()).isEqualTo(1000L);
+
+ verifyZeroInteractions(updater);
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegatorTest.java
index 4eede37eb18..966f98ef623 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegatorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegatorTest.java
@@ -24,10 +24,14 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import java.util.Optional;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Tracking;
import org.sonar.db.component.BranchType;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
@@ -37,40 +41,66 @@ public class IssueTrackingDelegatorTest {
@Mock
private ShortBranchTrackerExecution shortBranchTracker;
@Mock
+ private MergeBranchTrackerExecution mergeBranchTracker;
+ @Mock
private TrackerExecution tracker;
@Mock
private AnalysisMetadataHolder analysisMetadataHolder;
@Mock
private Component component;
+ @Mock
+ private Tracking<DefaultIssue, DefaultIssue> trackingResult;
private IssueTrackingDelegator underTest;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- underTest = new IssueTrackingDelegator(shortBranchTracker, tracker, analysisMetadataHolder);
+ underTest = new IssueTrackingDelegator(shortBranchTracker, mergeBranchTracker, tracker, analysisMetadataHolder);
+ when(tracker.track(component)).thenReturn(trackingResult);
+ when(mergeBranchTracker.track(component)).thenReturn(trackingResult);
+ when(shortBranchTracker.track(component)).thenReturn(trackingResult);
}
@Test
public void delegate_regular_tracker() {
when(analysisMetadataHolder.isShortLivingBranch()).thenReturn(false);
+ when(analysisMetadataHolder.getBranch()).thenReturn(Optional.empty());
underTest.track(component);
verify(tracker).track(component);
verifyZeroInteractions(shortBranchTracker);
+ verifyZeroInteractions(mergeBranchTracker);
+ }
+
+ @Test
+ public void delegate_merge_tracker() {
+ Branch branch = mock(Branch.class);
+ when(branch.getType()).thenReturn(BranchType.LONG);
+ when(branch.isMain()).thenReturn(false);
+ when(analysisMetadataHolder.getBranch()).thenReturn(Optional.of(branch));
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true);
+
+ underTest.track(component);
+
+ verify(mergeBranchTracker).track(component);
+ verifyZeroInteractions(tracker);
+ verifyZeroInteractions(shortBranchTracker);
+
}
@Test
public void delegate_short_branch_tracker() {
Branch branch = mock(Branch.class);
when(branch.getType()).thenReturn(BranchType.SHORT);
+ when(analysisMetadataHolder.getBranch()).thenReturn(Optional.empty());
when(analysisMetadataHolder.isShortLivingBranch()).thenReturn(true);
underTest.track(component);
verify(shortBranchTracker).track(component);
verifyZeroInteractions(tracker);
-
+ verifyZeroInteractions(mergeBranchTracker);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/MergeBranchTrackerExecutionTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/MergeBranchTrackerExecutionTest.java
new file mode 100644
index 00000000000..5dc408b7fb6
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/MergeBranchTrackerExecutionTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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 static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Input;
+import org.sonar.core.issue.tracking.Tracker;
+import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.server.computation.task.projectanalysis.component.Component;
+
+public class MergeBranchTrackerExecutionTest {
+ @Mock
+ private TrackerRawInputFactory rawInputFactory;
+ @Mock
+ private TrackerMergeBranchInputFactory mergeInputFactory;
+ @Mock
+ private Tracker<DefaultIssue, DefaultIssue> tracker;
+ @Mock
+ private Component component;
+
+ private MergeBranchTrackerExecution underTest;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ underTest = new MergeBranchTrackerExecution(rawInputFactory, mergeInputFactory, tracker);
+ }
+
+ @Test
+ public void testTracking() {
+ Input<DefaultIssue> rawInput = mock(Input.class);
+ Input<DefaultIssue> mergeInput = mock(Input.class);
+ Tracking<DefaultIssue, DefaultIssue> result = mock(Tracking.class);
+ when(rawInputFactory.create(component)).thenReturn(rawInput);
+ when(mergeInputFactory.create(component)).thenReturn(mergeInput);
+ when(tracker.track(rawInput, mergeInput)).thenReturn(result);
+
+ assertThat(underTest.track(component)).isEqualTo(result);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStepTest.java
index 1612e0d333c..bbb198d92ab 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStepTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStepTest.java
@@ -94,6 +94,41 @@ public class PersistIssuesStepTest extends BaseStepTest {
}
@Test
+ public void insert_copied_issue() {
+ RuleDefinitionDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
+ dbTester.rules().insert(rule);
+ OrganizationDto organizationDto = dbTester.organizations().insert();
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(organizationDto);
+ dbClient.componentDao().insert(session, project);
+ ComponentDto file = ComponentTesting.newFileDto(project, null);
+ dbClient.componentDao().insert(session, file);
+ session.commit();
+
+ issueCache.newAppender().append(new DefaultIssue()
+ .setKey("ISSUE")
+ .setType(RuleType.CODE_SMELL)
+ .setRuleKey(rule.getKey())
+ .setComponentUuid(file.uuid())
+ .setProjectUuid(project.uuid())
+ .setSeverity(Severity.BLOCKER)
+ .setStatus(Issue.STATUS_OPEN)
+ .setNew(false)
+ .setCopied(true)
+ .setType(RuleType.BUG)).close();
+
+ step.execute();
+
+ IssueDto result = dbClient.issueDao().selectOrFailByKey(session, "ISSUE");
+ assertThat(result.getKey()).isEqualTo("ISSUE");
+ assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
+ assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
+ assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
+ assertThat(result.getSeverity()).isEqualTo(Severity.BLOCKER);
+ assertThat(result.getStatus()).isEqualTo(Issue.STATUS_OPEN);
+ assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
+ }
+
+ @Test
public void insert_new_issue() {
RuleDefinitionDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
dbTester.rules().insert(rule);
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
index a92b911d44f..8ac7a205d58 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
@@ -96,10 +96,13 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
// all changes
private List<FieldDiffs> changes = null;
- // true if the the issue did not exist in the previous scan.
+ // true if the issue did not exist in the previous scan.
private boolean isNew = true;
- // True if the the issue did exist in the previous scan but not in the current one. That means
+ // true if the issue is being copied to a different branch
+ private boolean isCopied = false;
+
+ // True if the issue did exist in the previous scan but not in the current one. That means
// that this issue should be closed.
private boolean beingClosed = false;
@@ -412,6 +415,16 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
return isNew;
}
+ @Override
+ public boolean isCopied() {
+ return isCopied;
+ }
+
+ public DefaultIssue setCopied(boolean b) {
+ isCopied = b;
+ return this;
+ }
+
public DefaultIssue setNew(boolean b) {
isNew = b;
return this;
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java
index 9a1a2c3e5dc..29323740902 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java
@@ -161,6 +161,7 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder {
issue.setStatus(Issue.STATUS_OPEN);
issue.setCloseDate(null);
issue.setNew(true);
+ issue.setCopied(false);
issue.setBeingClosed(false);
issue.setOnDisabledRule(false);
return issue;
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java
index f9c36b3bc2a..247c2d32ebd 100644
--- a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java
@@ -59,6 +59,7 @@ public class DefaultIssueTest {
.setNew(true)
.setBeingClosed(true)
.setOnDisabledRule(true)
+ .setCopied(true)
.setChanged(true)
.setSendNotifications(true)
.setCreationDate(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-19"))
@@ -83,6 +84,7 @@ public class DefaultIssueTest {
assertThat(issue.authorLogin()).isEqualTo("steph");
assertThat(issue.checksum()).isEqualTo("c7b5db46591806455cf082bb348631e8");
assertThat(issue.isNew()).isTrue();
+ assertThat(issue.isCopied()).isTrue();
assertThat(issue.isBeingClosed()).isTrue();
assertThat(issue.isOnDisabledRule()).isTrue();
assertThat(issue.isChanged()).isTrue();
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java
index 4ed5b6750c9..9d4ffe43763 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java
@@ -207,6 +207,12 @@ public interface Issue extends Serializable {
boolean isNew();
/**
+ * During a scan returns true if the issue is copied from another branch.
+ * @since 6.6
+ */
+ boolean isCopied();
+
+ /**
* @deprecated since 5.5, replaced by {@link #effort()}
*/
@Deprecated
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueAdapterForFilter.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueAdapterForFilter.java
index 4b45945147e..389ace4a936 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueAdapterForFilter.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueAdapterForFilter.java
@@ -160,6 +160,11 @@ class DeprecatedIssueAdapterForFilter implements Issue {
throw unsupported();
}
+ @Override
+ public boolean isCopied() {
+ throw unsupported();
+ }
+
@Deprecated
@Override
public Duration debt() {
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueWrapper.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueWrapper.java
index 843d7c1e7e4..5c487b5bf4b 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueWrapper.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueWrapper.java
@@ -161,6 +161,11 @@ public class DeprecatedIssueWrapper implements Issue {
}
@Override
+ public boolean isCopied() {
+ return false;
+ }
+
+ @Override
public Duration debt() {
return null;
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/TrackedIssueAdapter.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/TrackedIssueAdapter.java
index 6db8b0abaa6..5d969e3a56e 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/TrackedIssueAdapter.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/TrackedIssueAdapter.java
@@ -114,6 +114,11 @@ public class TrackedIssueAdapter implements Issue {
}
@Override
+ public boolean isCopied() {
+ return false;
+ }
+
+ @Override
public Map<String, String> attributes() {
return new HashMap<>();
}