aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java75
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java6
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/ShortBranchIssueDto.java125
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml11
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml19
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java44
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java7
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java12
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java7
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java66
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java6
-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.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueStatusCopier.java54
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ResolvedShortBranchIssuesLoader.java57
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java6
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java5
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java177
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java18
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycleTest.java8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueStatusCopierTest.java96
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java216
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/tracking/SimpleTracker.java41
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracker.java197
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracking.java7
31 files changed, 1092 insertions, 216 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java b/server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java
new file mode 100644
index 00000000000..f13c0167797
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java
@@ -0,0 +1,75 @@
+/*
+ * 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.core.issue;
+
+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 Integer line;
+ private final String message;
+ private final String lineHash;
+ private final RuleKey ruleKey;
+ private final String status;
+ private final String resolution;
+
+ public ShortBranchIssue(@Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, @Nullable String resolution) {
+ this.line = line;
+ this.message = message;
+ this.lineHash = lineHash;
+ this.ruleKey = ruleKey;
+ this.status = status;
+ this.resolution = resolution;
+ }
+
+ @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;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public String getResolution() {
+ return resolution;
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
index c1bbe7fb0d9..7cd8e4ab309 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
@@ -59,6 +59,7 @@ import org.sonar.db.issue.IssueChangeDto;
import org.sonar.db.issue.IssueChangeMapper;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.issue.IssueMapper;
+import org.sonar.db.issue.ShortBranchIssueDto;
import org.sonar.db.loadedtemplate.LoadedTemplateDto;
import org.sonar.db.loadedtemplate.LoadedTemplateMapper;
import org.sonar.db.measure.MeasureDto;
@@ -162,6 +163,7 @@ public class MyBatis implements Startable {
confBuilder.loadAlias("IssueChange", IssueChangeDto.class);
confBuilder.loadAlias("KeyLongValue", KeyLongValue.class);
confBuilder.loadAlias("Issue", IssueDto.class);
+ confBuilder.loadAlias("ShortBranchIssue", ShortBranchIssueDto.class);
confBuilder.loadAlias("LoadedTemplate", LoadedTemplateDto.class);
confBuilder.loadAlias("Measure", MeasureDto.class);
confBuilder.loadAlias("NotificationQueue", NotificationQueueDto.class);
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
index 9e11072f09e..b52280819cf 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
@@ -343,4 +343,8 @@ public class ComponentDao implements Dao {
public void delete(DbSession session, long componentId) {
mapper(session).delete(componentId);
}
+
+ public List<KeyWithUuidDto> selectComponentKeysHavingIssuesToMerge(DbSession dbSession, String mergeBranchUuid) {
+ return mapper(dbSession).selectComponentKeysHavingIssuesToMerge(mergeBranchUuid);
+ }
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java
index 6d00aff0956..230515e95c0 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java
@@ -27,6 +27,7 @@ import java.util.Date;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.sonar.api.resources.Scopes;
@@ -532,4 +533,9 @@ public class ComponentDto {
public static String generateBranchKey(String componentKey, String branch) {
return format("%s%s%s", componentKey, BRANCH_KEY_SEPARATOR, branch);
}
+
+ public static String removeBranchFromKey(String componentKey) {
+ return StringUtils.substringBeforeLast(componentKey, ComponentDto.BRANCH_KEY_SEPARATOR);
+ }
+
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
index c99bab08f13..b317e0179a8 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
@@ -151,4 +151,6 @@ public interface ComponentMapper {
void delete(long componentId);
void updateTags(ComponentDto component);
+
+ List<KeyWithUuidDto> selectComponentKeysHavingIssuesToMerge(@Param("mergeBranchUuid") String mergeBranchUuid);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
index 40d148ec741..164f56d5b35 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
@@ -103,6 +103,10 @@ public class IssueDao implements Dao {
mapper(dbSession).scrollNonClosedByModuleOrProject(module.projectUuid(), likeModuleUuidPath, handler);
}
+ public List<ShortBranchIssueDto> selectResolvedOrConfirmedByComponentUuids(DbSession dbSession, Collection<String> componentUuids) {
+ return executeLargeInputs(componentUuids, mapper(dbSession)::selectResolvedOrConfirmedByComponentUuids);
+ }
+
public void insert(DbSession session, IssueDto dto) {
mapper(session).insert(dto);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
index d08d71999ab..4335a471126 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
@@ -32,6 +32,8 @@ public interface IssueMapper {
List<IssueDto> selectByKeys(List<String> keys);
+ List<ShortBranchIssueDto> selectResolvedOrConfirmedByComponentUuids(List<String> componentUuids);
+
void insert(IssueDto issue);
int update(IssueDto issue);
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ShortBranchIssueDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ShortBranchIssueDto.java
new file mode 100644
index 00000000000..3674cdc7af8
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ShortBranchIssueDto.java
@@ -0,0 +1,125 @@
+/*
+ * 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.db.issue;
+
+import com.google.common.base.Preconditions;
+import java.io.Serializable;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.ShortBranchIssue;
+import org.sonar.db.rule.RuleDefinitionDto;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public final class ShortBranchIssueDto implements Serializable {
+
+ private String kee;
+ private String message;
+ private Integer line;
+ private String checksum;
+ private String status;
+ private String resolution;
+
+ // joins
+ private String ruleKey;
+ private String ruleRepo;
+
+ public String getKey() {
+ return kee;
+ }
+
+ public ShortBranchIssueDto setKee(String s) {
+ this.kee = s;
+ return this;
+ }
+
+ @CheckForNull
+ public String getMessage() {
+ return message;
+ }
+
+ public ShortBranchIssueDto setMessage(@Nullable String s) {
+ this.message = s;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getLine() {
+ return line;
+ }
+
+ public ShortBranchIssueDto setLine(@Nullable Integer i) {
+ this.line = i;
+ return this;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public ShortBranchIssueDto setStatus(@Nullable String s) {
+ this.status = s;
+ return this;
+ }
+
+ @CheckForNull
+ public String getResolution() {
+ return resolution;
+ }
+
+ public ShortBranchIssueDto setResolution(@Nullable String s) {
+ this.resolution = s;
+ return this;
+ }
+
+ @CheckForNull
+ public String getChecksum() {
+ return checksum;
+ }
+
+ public ShortBranchIssueDto setChecksum(@Nullable String s) {
+ this.checksum = s;
+ return this;
+ }
+
+ public void setRuleRepo(String ruleRepo) {
+ this.ruleRepo = ruleRepo;
+ }
+
+ public void setRuleKey(String ruleKey) {
+ this.ruleKey = ruleKey;
+ }
+
+ public RuleKey getRuleKey() {
+ return RuleKey.of(ruleRepo, ruleKey);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ public static ShortBranchIssue toShortBranchIssue(ShortBranchIssueDto dto) {
+ return new ShortBranchIssue(dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getResolution());
+ }
+}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
index 2aaadf4d956..d35ba1ef4d6 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
@@ -655,4 +655,15 @@
DELETE FROM projects WHERE id=#{id,jdbcType=BIGINT}
</delete>
+ <select id="selectComponentKeysHavingIssuesToMerge" 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'
+ AND b.merge_branch_uuid = #{mergeBranchUuid,jdbcType=VARCHAR}
+ AND (i.status = 'CONFIRMED' OR i.status = 'RESOLVED')
+ </select>
+
</mapper>
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
index 744482c6cb1..fa9c34ec2d0 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
@@ -213,6 +213,25 @@
</foreach>
</select>
+ <select id="selectResolvedOrConfirmedByComponentUuids" parameterType="map" resultType="ShortBranchIssue">
+ select
+ i.kee as kee,
+ i.message as message,
+ i.line as line,
+ i.status as status,
+ i.resolution as resolution,
+ i.checksum as checksum,
+ r.plugin_rule_key as ruleKey,
+ r.plugin_name as ruleRepo
+ from issues i
+ inner join rules r on r.id = i.rule_id
+ where i.component_uuid in
+ <foreach collection="list" open="(" close=")" item="key" separator=",">
+ #{key,jdbcType=VARCHAR}
+ </foreach>
+ and (i.status = 'CONFIRMED' OR i.status = 'RESOLVED')
+ </select>
+
<select id="scrollNonClosedByModuleOrProject" parameterType="map" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
select
<include refid="issueColumns"/>
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java
index a9541a4762a..f44788c174f 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java
@@ -21,6 +21,7 @@ package org.sonar.db.issue;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
@@ -29,6 +30,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.System2;
+import org.sonar.core.issue.ShortBranchIssue;
import org.sonar.db.DbTester;
import org.sonar.db.RowNotFoundException;
import org.sonar.db.component.ComponentDto;
@@ -180,6 +182,48 @@ public class IssueDaoTest {
assertThat(accumulator.list).isEmpty();
}
+ @Test
+ public void selectResolvedOrConfirmedByComponentUuid() {
+ RuleDefinitionDto rule = db.rules().insert();
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto file = db.components().insertComponent(newFileDto(project));
+ IssueDto openIssue = db.issues().insert(rule, project, file, i -> i.setStatus("OPEN").setResolution(null));
+ IssueDto closedIssue = db.issues().insert(rule, project, file, i -> i.setStatus("CLOSED").setResolution("FIXED"));
+ IssueDto reopenedIssue = db.issues().insert(rule, project, file, i -> i.setStatus("REOPENED").setResolution(null));
+ IssueDto confirmedIssue = db.issues().insert(rule, project, file, i -> i.setStatus("CONFIRMED").setResolution(null));
+ IssueDto wontfixIssue = db.issues().insert(rule, project, file, i -> i.setStatus("RESOLVED").setResolution("WONTFIX"));
+ IssueDto fpIssue = db.issues().insert(rule, project, file, i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE"));
+
+ assertThat(underTest.selectResolvedOrConfirmedByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())))
+ .extracting("kee")
+ .containsOnly(confirmedIssue.getKey(), wontfixIssue.getKey(), fpIssue.getKey());
+ }
+
+ @Test
+ public void selectResolvedOrConfirmedByComponentUuid_should_correctly_map_required_fields() {
+ RuleDefinitionDto rule = db.rules().insert();
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto file = db.components().insertComponent(newFileDto(project));
+ IssueDto fpIssue = db.issues().insert(rule, project, file, i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE"));
+
+ ShortBranchIssueDto fp = underTest.selectResolvedOrConfirmedByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())).get(0);
+ assertThat(fp.getLine()).isEqualTo(fpIssue.getLine());
+ assertThat(fp.getMessage()).isEqualTo(fpIssue.getMessage());
+ assertThat(fp.getChecksum()).isEqualTo(fpIssue.getChecksum());
+ assertThat(fp.getRuleKey()).isEqualTo(fpIssue.getRuleKey());
+ assertThat(fp.getStatus()).isEqualTo(fpIssue.getStatus());
+ assertThat(fp.getResolution()).isEqualTo(fpIssue.getResolution());
+
+ assertThat(fp.getLine()).isNotNull();
+ assertThat(fp.getLine()).isNotZero();
+ assertThat(fp.getMessage()).isNotNull();
+ assertThat(fp.getChecksum()).isNotNull();
+ assertThat(fp.getChecksum()).isNotEmpty();
+ assertThat(fp.getRuleKey()).isNotNull();
+ assertThat(fp.getStatus()).isNotNull();
+ assertThat(fp.getResolution()).isNotNull();
+ }
+
private static IssueDto newIssueDto(String key) {
IssueDto dto = new IssueDto();
dto.setComponent(new ComponentDto().setDbKey("struts:Action").setId(123L).setUuid("component-uuid"));
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java
index e8dffaad582..a1bf583932c 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java
@@ -72,6 +72,13 @@ public interface AnalysisMetadataHolder {
boolean isShortLivingBranch();
/**
+ * Convenience method equivalent to do the check using {@link #getBranch()}
+ *
+ * @throws IllegalStateException if branch has not been set
+ */
+ boolean isLongLivingBranch();
+
+ /**
* @throws IllegalStateException if cross project duplication flag has not been set
*/
boolean isCrossProjectDuplicationEnabled();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java
index 654755b0454..fae786e5d59 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java
@@ -32,7 +32,7 @@ import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder {
-
+ private static final String BRANCH_NOT_SET = "Branch has not been set";
private final InitializedProperty<Organization> organization = new InitializedProperty<>();
private final InitializedProperty<String> uuid = new InitializedProperty<>();
private final InitializedProperty<Long> analysisDate = new InitializedProperty<>();
@@ -131,7 +131,7 @@ public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder
@Override
public Optional<Branch> getBranch() {
- checkState(branch.isInitialized(), "Branch has not been set");
+ checkState(branch.isInitialized(), BRANCH_NOT_SET);
return Optional.ofNullable(branch.getProperty());
}
@@ -189,9 +189,15 @@ public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder
}
public boolean isShortLivingBranch() {
- checkState(this.branch.isInitialized(), "Branch has not been set");
+ checkState(this.branch.isInitialized(), BRANCH_NOT_SET);
Branch prop = branch.getProperty();
return prop != null && prop.getType() == BranchType.SHORT;
}
+ public boolean isLongLivingBranch() {
+ checkState(this.branch.isInitialized(), BRANCH_NOT_SET);
+ Branch prop = branch.getProperty();
+ return prop != null && prop.getType() == BranchType.LONG;
+ }
+
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java
index cec721933f2..9817dadbfdf 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java
@@ -23,12 +23,13 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
-import org.apache.commons.lang.StringUtils;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import static org.sonar.db.component.ComponentDto.removeBranchFromKey;
+
/**
* Cache a map between component keys and uuids in the merge branch
*/
@@ -64,8 +65,4 @@ public class MergeBranchComponentUuids {
String cleanComponentKey = removeBranchFromKey(dbKey);
return uuidsByKey.get(cleanComponentKey);
}
-
- private static String removeBranchFromKey(String componentKey) {
- return StringUtils.substringBeforeLast(componentKey, ComponentDto.BRANCH_KEY_SEPARATOR);
- }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java
new file mode 100644
index 00000000000..110c723e9ec
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java
@@ -0,0 +1,66 @@
+/*
+ * 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.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.removeBranchFromKey;
+
+/**
+ * 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(removeBranchFromKey(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-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 2538dd69cbe..91b0a7d4da3 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
@@ -39,6 +39,7 @@ import org.sonar.server.computation.task.projectanalysis.component.Configuration
import org.sonar.server.computation.task.projectanalysis.component.DbIdsRepositoryImpl;
import org.sonar.server.computation.task.projectanalysis.component.DisabledComponentsHolderImpl;
import org.sonar.server.computation.task.projectanalysis.component.MergeBranchComponentUuids;
+import org.sonar.server.computation.task.projectanalysis.component.ShortBranchComponentsWithIssues;
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolderImpl;
import org.sonar.server.computation.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolderImpl;
import org.sonar.server.computation.task.projectanalysis.duplication.DuplicationMeasures;
@@ -63,6 +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.IssueTrackingDelegator;
import org.sonar.server.computation.task.projectanalysis.issue.IssueVisitors;
import org.sonar.server.computation.task.projectanalysis.issue.IssuesRepositoryVisitor;
@@ -71,6 +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.RuleRepositoryImpl;
import org.sonar.server.computation.task.projectanalysis.issue.RuleTagsCopier;
import org.sonar.server.computation.task.projectanalysis.issue.RuleTypeCopier;
@@ -172,6 +175,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
MutableTaskResultHolderImpl.class,
BatchReportReaderImpl.class,
MergeBranchComponentUuids.class,
+ ShortBranchComponentsWithIssues.class,
// repositories
LanguageRepositoryImpl.class,
@@ -246,6 +250,8 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
BaseIssuesLoader.class,
IssueTrackingDelegator.class,
BranchPersister.class,
+ ResolvedShortBranchIssuesLoader.class,
+ IssueStatusCopier.class,
// filemove
SourceSimilarityImpl.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 557de8de602..76cbed32dcf 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
@@ -19,8 +19,11 @@
*/
package org.sonar.server.computation.task.projectanalysis.issue;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
import org.sonar.server.computation.task.projectanalysis.component.TypeAwareVisitorAdapter;
@@ -34,13 +37,18 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
private final IssueLifecycle issueLifecycle;
private final IssueVisitors issueVisitors;
private final IssueTrackingDelegator issueTracking;
+ private final IssueStatusCopier issueStatusCopier;
+ private final AnalysisMetadataHolder analysisMetadataHolder;
- public IntegrateIssuesVisitor(IssueCache issueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors, IssueTrackingDelegator issueTracking) {
+ public IntegrateIssuesVisitor(IssueCache issueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors,
+ AnalysisMetadataHolder analysisMetadataHolder, IssueTrackingDelegator issueTracking, IssueStatusCopier issueStatusCopier) {
super(CrawlerDepthLimit.FILE, POST_ORDER);
this.issueCache = issueCache;
this.issueLifecycle = issueLifecycle;
this.issueVisitors = issueVisitors;
+ this.analysisMetadataHolder = analysisMetadataHolder;
this.issueTracking = issueTracking;
+ this.issueStatusCopier = issueStatusCopier;
}
@Override
@@ -60,8 +68,22 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
}
private void fillNewOpenIssues(Component component, Iterable<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
- for (DefaultIssue issue : issues) {
+ List<DefaultIssue> list = new ArrayList<>();
+
+ issues.forEach(issue -> {
issueLifecycle.initNewOpenIssue(issue);
+ list.add(issue);
+ });
+
+ if (list.isEmpty()) {
+ return;
+ }
+
+ if (analysisMetadataHolder.isLongLivingBranch()) {
+ issueStatusCopier.updateStatus(component, list);
+ }
+
+ for (DefaultIssue issue : list) {
process(component, issue, cacheAppender);
}
}
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 af327cf7090..868e6db0580 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
@@ -21,6 +21,7 @@ package org.sonar.server.computation.task.projectanalysis.issue;
import com.google.common.annotations.VisibleForTesting;
import java.util.Date;
+import javax.annotation.Nullable;
import org.sonar.api.issue.Issue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
@@ -76,6 +77,11 @@ public class IssueLifecycle {
}
}
+ public void copyResolution(DefaultIssue raw, String status, @Nullable String resolution) {
+ raw.setStatus(status);
+ raw.setResolution(resolution);
+ }
+
public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
raw.setKey(base.key());
raw.setNew(false);
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
new file mode 100644
index 00000000000..40f50c1d90c
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueStatusCopier.java
@@ -0,0 +1,54 @@
+/*
+ * 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
new file mode 100644
index 00000000000..4e3e5c33af9
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ResolvedShortBranchIssuesLoader.java
@@ -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 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/scm/ScmInfoImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java
index 61a953ab7a0..61a7f7a8573 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java
@@ -63,7 +63,7 @@ public class ScmInfoImpl implements ScmInfo {
@Override
public Changeset getChangesetForLine(int lineNumber) {
- checkArgument(lineNumber > 0 && lineNumber <= lineChangesets.length, String.format("There's no changeset on line %s", lineNumber));
+ checkArgument(lineNumber > 0 && lineNumber <= lineChangesets.length, "There's no changeset on line %s", lineNumber);
return lineChangesets[lineNumber - 1];
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java
index 01b243ad582..df4faae3fcf 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java
@@ -207,4 +207,10 @@ public class AnalysisMetadataHolderRule extends ExternalResource implements Muta
Branch property = this.branch.getProperty();
return property != null && property.getType() == BranchType.SHORT;
}
+
+ @Override
+ public boolean isLongLivingBranch() {
+ Branch property = this.branch.getProperty();
+ return property != null && property.getType() == BranchType.LONG;
+ }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java
index 4d6a7124f6a..787d5ef3e0d 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java
@@ -158,4 +158,9 @@ public class MutableAnalysisMetadataHolderRule extends ExternalResource implemen
public boolean isShortLivingBranch() {
return delegate.isShortLivingBranch();
}
+
+ @Override
+ public boolean isLongLivingBranch() {
+ return delegate.isLongLivingBranch();
+ }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java
new file mode 100644
index 00000000000..d9fca55cbca
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.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())).isEmpty();
+ 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())).isEmpty();
+ }
+
+ @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_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-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 2ad15bd7093..6dd63fa7f24 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
@@ -20,6 +20,7 @@
package org.sonar.server.computation.task.projectanalysis.issue;
import com.google.common.base.Optional;
+import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Rule;
@@ -46,7 +47,6 @@ import org.sonar.scanner.protocol.output.ScannerReport;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReaderRule;
import org.sonar.server.computation.task.projectanalysis.component.Component;
-import org.sonar.server.computation.task.projectanalysis.component.DefaultBranchImpl;
import org.sonar.server.computation.task.projectanalysis.component.MergeBranchComponentUuids;
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolderRule;
import org.sonar.server.computation.task.projectanalysis.component.TypeAwareVisitor;
@@ -112,8 +112,10 @@ public class IntegrateIssuesVisitorTest {
private IssueVisitor issueVisitor;
@Mock
private MergeBranchComponentUuids mergeBranchComponentsUuids;
+ @Mock
+ private IssueStatusCopier issueStatusCopier;
- ArgumentCaptor<DefaultIssue> defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
+ ArgumentCaptor<DefaultIssue> defaultIssueCaptor;
ComponentIssuesLoader issuesLoader = new ComponentIssuesLoader(dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule);
IssueTrackingDelegator trackingDelegator;
@@ -129,6 +131,7 @@ public class IntegrateIssuesVisitorTest {
MockitoAnnotations.initMocks(this);
IssueVisitors issueVisitors = new IssueVisitors(new IssueVisitor[] {issueVisitor});
+ defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
when(movedFilesRepository.getOriginalFile(any(Component.class))).thenReturn(Optional.<MovedFilesRepository.OriginalFile>absent());
TrackerRawInputFactory rawInputFactory = new TrackerRawInputFactory(treeRootHolder, reportReader, fileSourceRepository, new CommonRuleEngineImpl(), issueFilter);
@@ -142,11 +145,12 @@ public class IntegrateIssuesVisitorTest {
treeRootHolder.setRoot(PROJECT);
issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true);
- underTest = new IntegrateIssuesVisitor(issueCache, issueLifecycle, issueVisitors, trackingDelegator);
+ underTest = new IntegrateIssuesVisitor(issueCache, issueLifecycle, issueVisitors, analysisMetadataHolder, trackingDelegator, issueStatusCopier);
}
@Test
public void process_new_issue() throws Exception {
+ when(analysisMetadataHolder.isLongLivingBranch()).thenReturn(true);
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setMsg("the message")
.setRuleRepository("xoo")
@@ -159,10 +163,12 @@ public class IntegrateIssuesVisitorTest {
underTest.visitAny(FILE);
verify(issueLifecycle).initNewOpenIssue(defaultIssueCaptor.capture());
- assertThat(defaultIssueCaptor.getValue().ruleKey().rule()).isEqualTo("S001");
+ DefaultIssue capturedIssue = defaultIssueCaptor.getValue();
+ assertThat(capturedIssue.ruleKey().rule()).isEqualTo("S001");
- verify(issueLifecycle).doAutomaticTransition(defaultIssueCaptor.capture());
- assertThat(defaultIssueCaptor.getValue().ruleKey().rule()).isEqualTo("S001");
+ verify(issueStatusCopier).updateStatus(FILE, Collections.singletonList(capturedIssue));
+
+ verify(issueLifecycle).doAutomaticTransition(capturedIssue);
assertThat(newArrayList(issueCache.traverse())).hasSize(1);
}
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 f7e3a07c7b1..bdd6a058bff 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
@@ -76,6 +76,14 @@ public class IssueLifecycleTest {
}
@Test
+ public void copyResolution() {
+ DefaultIssue issue = new DefaultIssue();
+ underTest.copyResolution(issue, "status", "resolution");
+ assertThat(issue.resolution()).isEqualTo("resolution");
+ assertThat(issue.status()).isEqualTo("status");
+ }
+
+ @Test
public void copiedIssue() throws Exception {
DefaultIssue raw = new DefaultIssue()
.setNew(true)
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
new file mode 100644
index 00000000000..9716ed4dbca
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueStatusCopierTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java b/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java
new file mode 100644
index 00000000000..bdf7dd75d5d
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java
@@ -0,0 +1,216 @@
+/*
+ * 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.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 javax.annotation.Nonnull;
+import org.apache.commons.lang.StringUtils;
+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, SearchKeyFactory factory, boolean rejectMultipleMatches) {
+
+ if (tracking.isComplete()) {
+ return;
+ }
+
+ Multimap<SearchKey, BASE> baseSearch = ArrayListMultimap.create();
+ for (BASE base : tracking.getUnmatchedBases()) {
+ baseSearch.put(factory.create(base), base);
+ }
+
+ for (RAW raw : tracking.getUnmatchedRaws()) {
+ SearchKey rawKey = factory.create(raw);
+ Collection<BASE> bases = baseSearch.get(rawKey);
+ if (!bases.isEmpty()) {
+ Iterator<BASE> it = bases.iterator();
+ BASE match = it.next();
+ if (rejectMultipleMatches && it.hasNext()) {
+ continue;
+ }
+ // 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 {
+ }
+
+ private interface SearchKeyFactory {
+ SearchKey create(Trackable trackable);
+ }
+
+ private static class LineAndLineHashKey implements SearchKey {
+ private final RuleKey ruleKey;
+ private final String lineHash;
+ private final Integer line;
+
+ LineAndLineHashKey(Trackable trackable) {
+ this.ruleKey = trackable.getRuleKey();
+ this.line = trackable.getLine();
+ this.lineHash = StringUtils.defaultString(trackable.getLineHash(), "");
+ }
+
+ @Override
+ public boolean equals(@Nonnull Object o) {
+ if (this == o) {
+ return true;
+ }
+ LineAndLineHashKey that = (LineAndLineHashKey) o;
+ // start with most discriminant field
+ return Objects.equals(line, that.line)
+ && lineHash.equals(that.lineHash)
+ && ruleKey.equals(that.ruleKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ruleKey, lineHash, line != null ? line : 0);
+ }
+ }
+
+ protected enum LineAndLineHashKeyFactory implements SearchKeyFactory {
+ INSTANCE;
+ @Override
+ public SearchKey create(Trackable t) {
+ return new LineAndLineHashKey(t);
+ }
+ }
+
+ private static class LineHashAndMessageKey implements SearchKey {
+ private final RuleKey ruleKey;
+ private final String message;
+ private final String lineHash;
+
+ LineHashAndMessageKey(Trackable trackable) {
+ this.ruleKey = trackable.getRuleKey();
+ this.message = trackable.getMessage();
+ this.lineHash = StringUtils.defaultString(trackable.getLineHash(), "");
+ }
+
+ @Override
+ public boolean equals(@Nonnull Object o) {
+ if (this == o) {
+ return true;
+ }
+ LineHashAndMessageKey that = (LineHashAndMessageKey) o;
+ // start with most discriminant field
+ return lineHash.equals(that.lineHash)
+ && message.equals(that.message)
+ && ruleKey.equals(that.ruleKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ruleKey, message, lineHash);
+ }
+ }
+
+ protected enum LineHashAndMessageKeyFactory implements SearchKeyFactory {
+ INSTANCE;
+ @Override
+ public SearchKey create(Trackable t) {
+ return new LineHashAndMessageKey(t);
+ }
+ }
+
+ private static class LineAndMessageKey implements SearchKey {
+ private final RuleKey ruleKey;
+ private final String message;
+ private final Integer line;
+
+ LineAndMessageKey(Trackable trackable) {
+ this.ruleKey = trackable.getRuleKey();
+ this.message = trackable.getMessage();
+ this.line = trackable.getLine();
+ }
+
+ @Override
+ public boolean equals(@Nonnull Object o) {
+ if (this == o) {
+ return true;
+ }
+ LineAndMessageKey that = (LineAndMessageKey) o;
+ // start with most discriminant field
+ return Objects.equals(line, that.line)
+ && message.equals(that.message)
+ && ruleKey.equals(that.ruleKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ruleKey, message, line != null ? line : 0);
+ }
+ }
+
+ protected enum LineAndMessageKeyFactory implements SearchKeyFactory {
+ INSTANCE;
+ @Override
+ public SearchKey create(Trackable t) {
+ return new LineAndMessageKey(t);
+ }
+ }
+
+ private static class LineHashKey implements SearchKey {
+ private final RuleKey ruleKey;
+ private final String lineHash;
+
+ LineHashKey(Trackable trackable) {
+ this.ruleKey = trackable.getRuleKey();
+ this.lineHash = StringUtils.defaultString(trackable.getLineHash(), "");
+ }
+
+ @Override
+ public boolean equals(@Nonnull Object o) {
+ if (this == o) {
+ return true;
+ }
+ LineHashKey that = (LineHashKey) o;
+ // start with most discriminant field
+ return lineHash.equals(that.lineHash)
+ && ruleKey.equals(that.ruleKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ruleKey, lineHash);
+ }
+ }
+
+ protected enum LineHashKeyFactory implements SearchKeyFactory {
+ INSTANCE;
+ @Override
+ public SearchKey create(Trackable t) {
+ return new LineHashKey(t);
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/tracking/SimpleTracker.java b/sonar-core/src/main/java/org/sonar/core/issue/tracking/SimpleTracker.java
new file mode 100644
index 00000000000..4ae1833c8f4
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/issue/tracking/SimpleTracker.java
@@ -0,0 +1,41 @@
+/*
+ * 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.core.issue.tracking;
+
+import java.util.Collection;
+
+/**
+ * A simplified version of {@link Tracker}, which doesn't use line hash sequences nor block hash sequences and
+ * only has two steps instead of 5 steps.
+ */
+public class SimpleTracker<RAW extends Trackable, BASE extends Trackable> extends AbstractTracker<RAW, BASE> {
+
+ public Tracking<RAW, BASE> track(Collection<RAW> rawInput, Collection<BASE> baseInput) {
+ 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);
+
+ // 2. match issues with same rule, same message and same line hash
+ match(tracking, LineHashAndMessageKeyFactory.INSTANCE, true);
+
+ return tracking;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracker.java b/sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracker.java
index c13481a63f5..2039219ff89 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracker.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracker.java
@@ -19,22 +19,15 @@
*/
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.Objects;
-import javax.annotation.Nonnull;
-import org.apache.commons.lang.StringUtils;
import org.sonar.api.batch.ScannerSide;
import org.sonar.api.batch.InstantiationStrategy;
-import org.sonar.api.rule.RuleKey;
@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
@ScannerSide
-public class Tracker<RAW extends Trackable, BASE extends Trackable> {
+public class Tracker<RAW extends Trackable, BASE extends Trackable> extends AbstractTracker<RAW, BASE> {
public Tracking<RAW, BASE> track(Input<RAW> rawInput, Input<BASE> baseInput) {
- Tracking<RAW, BASE> tracking = new Tracking<>(rawInput, baseInput);
+ 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);
@@ -60,190 +53,4 @@ public class Tracker<RAW extends Trackable, BASE extends Trackable> {
new BlockRecognizer<RAW, BASE>().match(rawInput, baseInput, tracking);
}
}
-
- private void match(Tracking<RAW, BASE> tracking, SearchKeyFactory factory) {
- if (tracking.isComplete()) {
- return;
- }
-
- Multimap<SearchKey, BASE> baseSearch = ArrayListMultimap.create();
- for (BASE base : tracking.getUnmatchedBases()) {
- baseSearch.put(factory.create(base), base);
- }
-
- for (RAW raw : tracking.getUnmatchedRaws()) {
- SearchKey rawKey = factory.create(raw);
- Collection<BASE> bases = baseSearch.get(rawKey);
- if (!bases.isEmpty()) {
- // 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.
- BASE match = bases.iterator().next();
- tracking.match(raw, match);
- baseSearch.remove(rawKey, match);
- }
- }
- }
-
- private interface SearchKey {
- }
-
- private interface SearchKeyFactory {
- SearchKey create(Trackable trackable);
- }
-
- private static class LineAndLineHashKey implements SearchKey {
- private final RuleKey ruleKey;
- private final String lineHash;
- private final Integer line;
-
- LineAndLineHashKey(Trackable trackable) {
- this.ruleKey = trackable.getRuleKey();
- this.line = trackable.getLine();
- this.lineHash = StringUtils.defaultString(trackable.getLineHash(), "");
- }
-
- @Override
- public boolean equals(@Nonnull Object o) {
- if (this == o) {
- return true;
- }
- LineAndLineHashKey that = (LineAndLineHashKey) o;
- // start with most discriminant field
- return Objects.equals(line, that.line)
- && lineHash.equals(that.lineHash)
- && ruleKey.equals(that.ruleKey);
- }
-
- @Override
- public int hashCode() {
- int result = ruleKey.hashCode();
- result = 31 * result + lineHash.hashCode();
- result = 31 * result + (line != null ? line.hashCode() : 0);
- return result;
- }
- }
-
- private enum LineAndLineHashKeyFactory implements SearchKeyFactory {
- INSTANCE;
- @Override
- public SearchKey create(Trackable t) {
- return new LineAndLineHashKey(t);
- }
- }
-
- private static class LineHashAndMessageKey implements SearchKey {
- private final RuleKey ruleKey;
- private final String message;
- private final String lineHash;
-
- LineHashAndMessageKey(Trackable trackable) {
- this.ruleKey = trackable.getRuleKey();
- this.message = trackable.getMessage();
- this.lineHash = StringUtils.defaultString(trackable.getLineHash(), "");
- }
-
- @Override
- public boolean equals(@Nonnull Object o) {
- if (this == o) {
- return true;
- }
- LineHashAndMessageKey that = (LineHashAndMessageKey) o;
- // start with most discriminant field
- return lineHash.equals(that.lineHash)
- && message.equals(that.message)
- && ruleKey.equals(that.ruleKey);
- }
-
- @Override
- public int hashCode() {
- int result = ruleKey.hashCode();
- result = 31 * result + message.hashCode();
- result = 31 * result + lineHash.hashCode();
- return result;
- }
- }
-
- private enum LineHashAndMessageKeyFactory implements SearchKeyFactory {
- INSTANCE;
- @Override
- public SearchKey create(Trackable t) {
- return new LineHashAndMessageKey(t);
- }
- }
-
- private static class LineAndMessageKey implements SearchKey {
- private final RuleKey ruleKey;
- private final String message;
- private final Integer line;
-
- LineAndMessageKey(Trackable trackable) {
- this.ruleKey = trackable.getRuleKey();
- this.message = trackable.getMessage();
- this.line = trackable.getLine();
- }
-
- @Override
- public boolean equals(@Nonnull Object o) {
- if (this == o) {
- return true;
- }
- LineAndMessageKey that = (LineAndMessageKey) o;
- // start with most discriminant field
- return Objects.equals(line, that.line)
- && message.equals(that.message)
- && ruleKey.equals(that.ruleKey);
- }
-
- @Override
- public int hashCode() {
- int result = ruleKey.hashCode();
- result = 31 * result + message.hashCode();
- result = 31 * result + (line != null ? line.hashCode() : 0);
- return result;
- }
- }
-
- private enum LineAndMessageKeyFactory implements SearchKeyFactory {
- INSTANCE;
- @Override
- public SearchKey create(Trackable t) {
- return new LineAndMessageKey(t);
- }
- }
-
- private static class LineHashKey implements SearchKey {
- private final RuleKey ruleKey;
- private final String lineHash;
-
- LineHashKey(Trackable trackable) {
- this.ruleKey = trackable.getRuleKey();
- this.lineHash = StringUtils.defaultString(trackable.getLineHash(), "");
- }
-
- @Override
- public boolean equals(@Nonnull Object o) {
- if (this == o) {
- return true;
- }
- LineHashKey that = (LineHashKey) o;
- // start with most discriminant field
- return lineHash.equals(that.lineHash)
- && ruleKey.equals(that.ruleKey);
- }
-
- @Override
- public int hashCode() {
- int result = ruleKey.hashCode();
- result = 31 * result + lineHash.hashCode();
- return result;
- }
- }
-
- private enum LineHashKeyFactory implements SearchKeyFactory {
- INSTANCE;
- @Override
- public SearchKey create(Trackable t) {
- return new LineHashKey(t);
- }
- }
}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracking.java b/sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracking.java
index 844061e380e..99ba9c7b379 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracking.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/tracking/Tracking.java
@@ -39,12 +39,11 @@ public class Tracking<RAW extends Trackable, BASE extends Trackable> {
private final Collection<BASE> bases;
private final Predicate<RAW> unmatchedRawPredicate = raw -> !rawToBase.containsKey(raw);
-
private final Predicate<BASE> unmatchedBasePredicate = raw -> !baseToRaw.containsKey(raw);
- public Tracking(Input<RAW> rawInput, Input<BASE> baseInput) {
- this.raws = rawInput.getIssues();
- this.bases = baseInput.getIssues();
+ public Tracking(Collection<RAW> rawInput, Collection<BASE> baseInput) {
+ this.raws = rawInput;
+ this.bases = baseInput;
}
/**