diff options
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; } /** |