aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java10
-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.java7
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml2
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml5
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java10
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMergerTest.java53
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java5
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java40
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/tracking/SimpleTracker.java4
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java7
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java16
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueFromWs.java9
16 files changed, 127 insertions, 53 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
index f6c6bcd977b..e9058d626b4 100644
--- 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
@@ -19,6 +19,7 @@
*/
package org.sonar.core.issue;
+import java.util.Date;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@@ -34,8 +35,9 @@ public class ShortBranchIssue implements Trackable {
private final RuleKey ruleKey;
private final String status;
private final String branchName;
+ private final Date creationDate;
- public ShortBranchIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName) {
+ public ShortBranchIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName, Date creationDate) {
this.key = key;
this.line = line;
this.message = message;
@@ -43,6 +45,7 @@ public class ShortBranchIssue implements Trackable {
this.ruleKey = ruleKey;
this.status = status;
this.branchName = branchName;
+ this.creationDate = creationDate;
}
public String getKey() {
@@ -81,6 +84,11 @@ public class ShortBranchIssue implements Trackable {
}
@Override
+ public Date getCreationDate() {
+ return creationDate;
+ }
+
+ @Override
public int hashCode() {
return key.hashCode();
}
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 164f56d5b35..ab8ccd3871f 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,8 +103,8 @@ 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 List<ShortBranchIssueDto> selectOpenByComponentUuids(DbSession dbSession, Collection<String> componentUuids) {
+ return executeLargeInputs(componentUuids, mapper(dbSession)::selectOpenByComponentUuids);
}
public void insert(DbSession session, IssueDto 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 4335a471126..75ec4c9d542 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,7 +32,7 @@ public interface IssueMapper {
List<IssueDto> selectByKeys(List<String> keys);
- List<ShortBranchIssueDto> selectResolvedOrConfirmedByComponentUuids(List<String> componentUuids);
+ List<ShortBranchIssueDto> selectOpenByComponentUuids(List<String> componentUuids);
void insert(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
index 3c1901041e4..79208ddd20d 100644
--- 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
@@ -27,6 +27,8 @@ import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.rule.RuleKey;
import org.sonar.core.issue.ShortBranchIssue;
+import static org.sonar.api.utils.DateUtils.longToDate;
+
public final class ShortBranchIssueDto implements Serializable {
private String kee;
@@ -34,6 +36,7 @@ public final class ShortBranchIssueDto implements Serializable {
private Integer line;
private String checksum;
private String status;
+ private Long issueCreationDate;
// joins
private String ruleKey;
@@ -114,7 +117,7 @@ public final class ShortBranchIssueDto implements Serializable {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
- public static ShortBranchIssue toShortBranchIssue(ShortBranchIssueDto dto) {
- return new ShortBranchIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getBranchName());
+ public ShortBranchIssue toShortBranchIssue() {
+ return new ShortBranchIssue(getKey(), getLine(), getMessage(), getChecksum(), getRuleKey(), getStatus(), getBranchName(), longToDate(issueCreationDate));
}
}
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 0a4b557880c..5d7e051032f 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
@@ -665,7 +665,7 @@
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')
+ AND i.status != 'CLOSED'
</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 09d43435502..545963ea5e5 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,13 +213,14 @@
</foreach>
</select>
- <select id="selectResolvedOrConfirmedByComponentUuids" parameterType="map" resultType="ShortBranchIssue">
+ <select id="selectOpenByComponentUuids" parameterType="map" resultType="ShortBranchIssue">
select
i.kee as kee,
i.message as message,
i.line as line,
i.status as status,
i.checksum as checksum,
+ i.issue_creation_date as issueCreationDate,
r.plugin_rule_key as ruleKey,
r.plugin_name as ruleRepo,
b.kee as branchName
@@ -230,7 +231,7 @@
<foreach collection="list" open="(" close=")" item="key" separator=",">
#{key,jdbcType=VARCHAR}
</foreach>
- and (i.status = 'CONFIRMED' OR i.status = 'RESOLVED')
+ and i.status &lt;&gt; 'CLOSED'
</select>
<select id="scrollNonClosedByModuleOrProject" parameterType="map" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
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 8c4682215c1..cc31fa3d9c7 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
@@ -184,7 +184,7 @@ public class IssueDaoTest {
}
@Test
- public void selectResolvedOrConfirmedByComponentUuid() {
+ public void selectOpenByComponentUuid() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertMainBranch();
ComponentDto projectBranch = db.components().insertProjectBranch(project,
@@ -200,13 +200,13 @@ public class IssueDaoTest {
IssueDto wontfixIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX));
IssueDto fpIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FALSE_POSITIVE));
- assertThat(underTest.selectResolvedOrConfirmedByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())))
+ assertThat(underTest.selectOpenByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())))
.extracting("kee")
- .containsOnly(confirmedIssue.getKey(), wontfixIssue.getKey(), fpIssue.getKey());
+ .containsOnly(openIssue.getKey(), reopenedIssue.getKey(), confirmedIssue.getKey(), wontfixIssue.getKey(), fpIssue.getKey());
}
@Test
- public void selectResolvedOrConfirmedByComponentUuid_should_correctly_map_required_fields() {
+ public void selectOpenByComponentUuid_should_correctly_map_required_fields() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertMainBranch();
ComponentDto projectBranch = db.components().insertProjectBranch(project,
@@ -216,7 +216,7 @@ public class IssueDaoTest {
ComponentDto file = db.components().insertComponent(newFileDto(projectBranch));
IssueDto fpIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE"));
- ShortBranchIssueDto fp = underTest.selectResolvedOrConfirmedByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())).get(0);
+ ShortBranchIssueDto fp = underTest.selectOpenByComponentUuids(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());
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java
index 1beca5a1b64..cf91a53b07c 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java
@@ -56,7 +56,7 @@ public class ShortBranchIssuesLoader {
return Collections.emptyList();
}
try (DbSession session = dbClient.openSession(false)) {
- return dbClient.issueDao().selectResolvedOrConfirmedByComponentUuids(session, uuids)
+ return dbClient.issueDao().selectOpenByComponentUuids(session, uuids)
.stream()
.map(ShortBranchIssueDto::toShortBranchIssue)
.collect(Collectors.toList());
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
index d9fca55cbca..41f1dd43c09 100644
--- 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
@@ -111,7 +111,7 @@ public class ShortBranchComponentsWithIssuesTest {
setRootUuid(long1.uuid());
assertThat(underTest.getUuids(fileWithNoIssues.getKey())).isEmpty();
- assertThat(underTest.getUuids(fileWithOneOpenIssue.getKey())).isEmpty();
+ assertThat(underTest.getUuids(fileWithOneOpenIssue.getKey())).containsOnly(fileWithOneOpenIssue.uuid());
assertThat(underTest.getUuids(fileWithOneResolvedIssue.getKey())).containsOnly(fileWithOneResolvedIssue.uuid());
assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssues.getKey())).containsOnly(fileWithOneOpenTwoResolvedIssues.uuid());
@@ -128,7 +128,7 @@ public class ShortBranchComponentsWithIssuesTest {
assertThat(underTest.getUuids(fileWithOneResolvedIssue.getKey())).isEmpty();
assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong2.getKey())).containsOnly(fileWithOneResolvedIssueOnLong2.uuid());
- assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong2.getKey())).isEmpty();
+ assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong2.getKey())).containsOnly(fileWithOneOpenIssueOnLong2.uuid());
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMergerTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMergerTest.java
index 134f83b9d4e..c3b720e872c 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMergerTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMergerTest.java
@@ -20,9 +20,12 @@
package org.sonar.server.computation.task.projectanalysis.issue;
import com.google.common.collect.ImmutableMap;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Date;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
@@ -62,7 +65,7 @@ public class ShortBranchIssueMergerTest {
@Test
public void do_nothing_if_no_match() {
when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.emptyList());
- DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null);
+ DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null, new Date());
copier.tryMerge(component, Collections.singleton(i));
verify(resolvedShortBranchIssuesLoader).loadCandidateIssuesForMergingInTargetBranch(component);
@@ -71,7 +74,7 @@ public class ShortBranchIssueMergerTest {
@Test
public void do_nothing_if_no_new_issue() {
- DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null);
+ DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null, new Date());
when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.singleton(newShortBranchIssue(i, "myBranch")));
copier.tryMerge(component, Collections.emptyList());
@@ -81,9 +84,9 @@ public class ShortBranchIssueMergerTest {
@Test
public void update_status_on_matches() {
- DefaultIssue issue1 = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null);
+ DefaultIssue issue1 = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null, new Date());
ShortBranchIssue shortBranchIssue = newShortBranchIssue(issue1, "myBranch");
- DefaultIssue newIssue = createIssue("issue2", "rule1", Issue.STATUS_OPEN, null);
+ DefaultIssue newIssue = createIssue("issue2", "rule1", Issue.STATUS_OPEN, null, new Date());
when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.singleton(shortBranchIssue));
when(resolvedShortBranchIssuesLoader.loadDefaultIssuesWithChanges(anyListOf(ShortBranchIssue.class))).thenReturn(ImmutableMap.of(shortBranchIssue, issue1));
@@ -96,11 +99,11 @@ public class ShortBranchIssueMergerTest {
@Test
public void prefer_resolved_issues() {
- ShortBranchIssue shortBranchIssue1 = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null), "myBranch1");
- ShortBranchIssue shortBranchIssue2 = newShortBranchIssue(createIssue("issue2", "rule1", Issue.STATUS_CONFIRMED, null), "myBranch2");
- DefaultIssue issue3 = createIssue("issue3", "rule1", Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE);
+ ShortBranchIssue shortBranchIssue1 = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_REOPENED, null, new Date()), "myBranch1");
+ ShortBranchIssue shortBranchIssue2 = newShortBranchIssue(createIssue("issue2", "rule1", Issue.STATUS_CONFIRMED, null, new Date()), "myBranch2");
+ DefaultIssue issue3 = createIssue("issue3", "rule1", Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE, new Date());
ShortBranchIssue shortBranchIssue3 = newShortBranchIssue(issue3, "myBranch3");
- DefaultIssue newIssue = createIssue("newIssue", "rule1", Issue.STATUS_OPEN, null);
+ DefaultIssue newIssue = createIssue("newIssue", "rule1", Issue.STATUS_OPEN, null, new Date());
when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Arrays.asList(shortBranchIssue1, shortBranchIssue2, shortBranchIssue3));
when(resolvedShortBranchIssuesLoader.loadDefaultIssuesWithChanges(anyListOf(ShortBranchIssue.class))).thenReturn(ImmutableMap.of(shortBranchIssue3, issue3));
@@ -108,7 +111,36 @@ public class ShortBranchIssueMergerTest {
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(newIssue, issue3, "myBranch3");
}
- private static DefaultIssue createIssue(String key, String ruleKey, String status, @Nullable String resolution) {
+ @Test
+ public void prefer_confirmed_issues() {
+ ShortBranchIssue shortBranchIssue1 = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_REOPENED, null, new Date()), "myBranch1");
+ ShortBranchIssue shortBranchIssue2 = newShortBranchIssue(createIssue("issue2", "rule1", Issue.STATUS_OPEN, null, new Date()), "myBranch2");
+ DefaultIssue issue3 = createIssue("issue3", "rule1", Issue.STATUS_CONFIRMED, null, new Date());
+ ShortBranchIssue shortBranchIssue3 = newShortBranchIssue(issue3, "myBranch3");
+ DefaultIssue newIssue = createIssue("newIssue", "rule1", Issue.STATUS_OPEN, null, new Date());
+
+ when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Arrays.asList(shortBranchIssue1, shortBranchIssue2, shortBranchIssue3));
+ when(resolvedShortBranchIssuesLoader.loadDefaultIssuesWithChanges(anyListOf(ShortBranchIssue.class))).thenReturn(ImmutableMap.of(shortBranchIssue3, issue3));
+ copier.tryMerge(component, Collections.singleton(newIssue));
+ verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(newIssue, issue3, "myBranch3");
+ }
+
+ @Test
+ public void prefer_older_issues() {
+ Instant now = Instant.now();
+ ShortBranchIssue shortBranchIssue1 = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_REOPENED, null, Date.from(now.plus(2, ChronoUnit.SECONDS))), "myBranch1");
+ ShortBranchIssue shortBranchIssue2 = newShortBranchIssue(createIssue("issue2", "rule1", Issue.STATUS_OPEN, null, Date.from(now.plus(1, ChronoUnit.SECONDS))), "myBranch2");
+ DefaultIssue issue3 = createIssue("issue3", "rule1", Issue.STATUS_OPEN, null, Date.from(now));
+ ShortBranchIssue shortBranchIssue3 = newShortBranchIssue(issue3, "myBranch3");
+ DefaultIssue newIssue = createIssue("newIssue", "rule1", Issue.STATUS_OPEN, null, new Date());
+
+ when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Arrays.asList(shortBranchIssue1, shortBranchIssue2, shortBranchIssue3));
+ when(resolvedShortBranchIssuesLoader.loadDefaultIssuesWithChanges(anyListOf(ShortBranchIssue.class))).thenReturn(ImmutableMap.of(shortBranchIssue3, issue3));
+ copier.tryMerge(component, Collections.singleton(newIssue));
+ verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(newIssue, issue3, "myBranch3");
+ }
+
+ private static DefaultIssue createIssue(String key, String ruleKey, String status, @Nullable String resolution, Date creationDate) {
DefaultIssue issue = new DefaultIssue();
issue.setKey(key);
issue.setRuleKey(RuleKey.of("repo", ruleKey));
@@ -116,10 +148,11 @@ public class ShortBranchIssueMergerTest {
issue.setLine(1);
issue.setStatus(status);
issue.setResolution(resolution);
+ issue.setCreationDate(creationDate);
return issue;
}
private ShortBranchIssue newShortBranchIssue(DefaultIssue i, String originBranch) {
- return new ShortBranchIssue(i.key(), i.line(), i.message(), i.getLineHash(), i.ruleKey(), i.status(), originBranch);
+ return new ShortBranchIssue(i.key(), i.line(), i.message(), i.getLineHash(), i.ruleKey(), i.status(), originBranch, i.creationDate());
}
}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
index 02032a1c62d..c7b0626e826 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
@@ -653,4 +653,9 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
public String getStatus() {
return status;
}
+
+ @Override
+ public Date getCreationDate() {
+ return creationDate;
+ }
}
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
index 69096993324..60cc9b05782 100644
--- 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
@@ -29,13 +29,11 @@ import org.apache.commons.lang.StringUtils;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
+import static java.util.Comparator.comparing;
+
public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
protected void match(Tracking<RAW, BASE> tracking, Function<Trackable, SearchKey> searchKeyFactory) {
- match(tracking, searchKeyFactory, false);
- }
-
- protected void match(Tracking<RAW, BASE> tracking, Function<Trackable, SearchKey> searchKeyFactory, boolean preferResolved) {
if (tracking.isComplete()) {
return;
@@ -49,21 +47,25 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
for (RAW raw : tracking.getUnmatchedRaws()) {
SearchKey rawKey = searchKeyFactory.apply(raw);
Collection<BASE> bases = baseSearch.get(rawKey);
- if (!bases.isEmpty()) {
- BASE match;
- if (preferResolved) {
- match = bases.stream()
- .filter(i -> Issue.STATUS_RESOLVED.equals(i.getStatus()))
- .findFirst()
- .orElse(bases.iterator().next());
- } else {
- // TODO taking the first one. Could be improved if there are more than 2 issues on the same line.
- // Message could be checked to take the best one.
- match = bases.iterator().next();
- }
- tracking.match(raw, match);
- baseSearch.remove(rawKey, match);
- }
+ bases.stream()
+ .sorted(comparing(this::statusRank).reversed()
+ .thenComparing(comparing(Trackable::getCreationDate)))
+ .findFirst()
+ .ifPresent(match -> {
+ tracking.match(raw, match);
+ baseSearch.remove(rawKey, match);
+ });
+ }
+ }
+
+ private int statusRank(BASE i) {
+ switch (i.getStatus()) {
+ case Issue.STATUS_RESOLVED:
+ return 2;
+ case Issue.STATUS_CONFIRMED:
+ return 1;
+ default:
+ return 0;
}
}
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
index 54fd8ea2bba..b5fb56a6f26 100644
--- 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
@@ -31,10 +31,10 @@ public class SimpleTracker<RAW extends Trackable, BASE extends Trackable> extend
Tracking<RAW, BASE> tracking = new Tracking<>(rawInput, baseInput);
// 1. match issues with same rule, same line and same line hash, but not necessarily with same message
- match(tracking, LineAndLineHashKey::new, true);
+ match(tracking, LineAndLineHashKey::new);
// 2. match issues with same rule, same message and same line hash
- match(tracking, LineHashAndMessageKey::new, true);
+ match(tracking, LineHashAndMessageKey::new);
return tracking;
}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java b/sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java
index 0d04b81e366..6c6af607afc 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java
@@ -19,8 +19,10 @@
*/
package org.sonar.core.issue.tracking;
+import java.util.Date;
import javax.annotation.CheckForNull;
import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.DefaultIssue;
public interface Trackable {
@@ -42,4 +44,9 @@ public interface Trackable {
RuleKey getRuleKey();
String getStatus();
+
+ /**
+ * Functional creation date for the issue. See {@link DefaultIssue#creationDate()}
+ */
+ Date getCreationDate();
}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java b/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java
index 59e106f1433..c894c65fcdb 100644
--- a/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java
@@ -21,6 +21,7 @@ package org.sonar.core.issue.tracking;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Date;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.commons.codec.digest.DigestUtils;
@@ -186,7 +187,7 @@ public class TrackerTest {
@Test
public void do_not_fail_if_raw_line_does_not_exist() {
FakeInput baseInput = new FakeInput();
- FakeInput rawInput = new FakeInput("H1").addIssue(new Issue(200, "H200", RULE_SYSTEM_PRINT, "msg", org.sonar.api.issue.Issue.STATUS_OPEN));
+ FakeInput rawInput = new FakeInput("H1").addIssue(new Issue(200, "H200", RULE_SYSTEM_PRINT, "msg", org.sonar.api.issue.Issue.STATUS_OPEN, new Date()));
Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput);
@@ -386,12 +387,14 @@ public class TrackerTest {
private final Integer line;
private final String message, lineHash;
private final String status;
+ private final Date creationDate;
- Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status) {
+ Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status, Date creationDate) {
this.line = line;
this.lineHash = lineHash;
this.ruleKey = ruleKey;
this.status = status;
+ this.creationDate = creationDate;
this.message = trim(message);
}
@@ -419,6 +422,11 @@ public class TrackerTest {
public String getStatus() {
return status;
}
+
+ @Override
+ public Date getCreationDate() {
+ return creationDate;
+ }
}
private static class FakeInput implements Input<Issue> {
@@ -438,7 +446,7 @@ public class TrackerTest {
}
Issue createIssueOnLine(int line, RuleKey ruleKey, String message) {
- Issue issue = new Issue(line, lineHashes.get(line - 1), ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN);
+ Issue issue = new Issue(line, lineHashes.get(line - 1), ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN, new Date());
issues.add(issue);
return issue;
}
@@ -447,7 +455,7 @@ public class TrackerTest {
* No line (line 0)
*/
Issue createIssue(RuleKey ruleKey, String message) {
- Issue issue = new Issue(null, "", ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN);
+ Issue issue = new Issue(null, "", ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN, new Date());
issues.add(issue);
return issue;
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueFromWs.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueFromWs.java
index a0888d20341..2ddba9e2015 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueFromWs.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueFromWs.java
@@ -19,8 +19,10 @@
*/
package org.sonar.scanner.issue.tracking;
+import java.util.Date;
import javax.annotation.CheckForNull;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.DateUtils;
import org.sonar.core.issue.tracking.Trackable;
import static org.apache.commons.lang.StringUtils.trim;
@@ -65,7 +67,12 @@ public class ServerIssueFromWs implements Trackable {
@Override
public String getStatus() {
- throw new UnsupportedOperationException();
+ return dto.getStatus();
+ }
+
+ @Override
+ public Date getCreationDate() {
+ return DateUtils.longToDate(dto.getCreationDate());
}
}