import org.sonar.core.issue.FieldDiffs; | import org.sonar.core.issue.FieldDiffs; | ||||
import org.sonar.core.issue.IssueChangeContext; | import org.sonar.core.issue.IssueChangeContext; | ||||
import org.sonar.core.util.Uuids; | import org.sonar.core.util.Uuids; | ||||
import org.sonar.db.component.KeyType; | |||||
import org.sonar.server.issue.IssueFieldsSetter; | import org.sonar.server.issue.IssueFieldsSetter; | ||||
import org.sonar.server.issue.workflow.IssueWorkflow; | import org.sonar.server.issue.workflow.IssueWorkflow; | ||||
raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_LONG_BRANCH, fromLongBranchName, analysisMetadataHolder.getBranch().getName()); | raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_LONG_BRANCH, fromLongBranchName, analysisMetadataHolder.getBranch().getName()); | ||||
} | } | ||||
public void mergeConfirmedOrResolvedFromShortLivingBranch(DefaultIssue raw, DefaultIssue base, String fromShortBranchName) { | |||||
public void mergeConfirmedOrResolvedFromShortLivingBranchOrPr(DefaultIssue raw, DefaultIssue base, KeyType branchType, String fromShortBranchNameOrPR) { | |||||
copyAttributesOfIssueFromOtherBranch(raw, base); | copyAttributesOfIssueFromOtherBranch(raw, base); | ||||
raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_SHORT_BRANCH, fromShortBranchName, | |||||
analysisMetadataHolder.isPullRequest() ? analysisMetadataHolder.getPullRequestKey() : analysisMetadataHolder.getBranch().getName()); | |||||
String from = (branchType == KeyType.PULL_REQUEST ? "#" : "") + fromShortBranchNameOrPR; | |||||
String to = analysisMetadataHolder.isPullRequest() ? ("#" + analysisMetadataHolder.getPullRequestKey()) : analysisMetadataHolder.getBranch().getName(); | |||||
raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_SHORT_BRANCH, from, to); | |||||
} | } | ||||
private void copyAttributesOfIssueFromOtherBranch(DefaultIssue to, DefaultIssue from) { | private void copyAttributesOfIssueFromOtherBranch(DefaultIssue to, DefaultIssue from) { |
import javax.annotation.concurrent.Immutable; | import javax.annotation.concurrent.Immutable; | ||||
import org.sonar.api.rule.RuleKey; | import org.sonar.api.rule.RuleKey; | ||||
import org.sonar.core.issue.tracking.Trackable; | import org.sonar.core.issue.tracking.Trackable; | ||||
import org.sonar.db.component.KeyType; | |||||
@Immutable | @Immutable | ||||
public class SiblingIssue implements Trackable { | public class SiblingIssue implements Trackable { | ||||
private final String lineHash; | private final String lineHash; | ||||
private final RuleKey ruleKey; | private final RuleKey ruleKey; | ||||
private final String status; | private final String status; | ||||
private final String branchName; | |||||
private final String branchKey; | |||||
private final KeyType branchType; | |||||
private final Date updateDate; | private final Date updateDate; | ||||
public SiblingIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName, Date updateDate) { | |||||
public SiblingIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchKey, KeyType branchType, | |||||
Date updateDate) { | |||||
this.key = key; | this.key = key; | ||||
this.line = line; | this.line = line; | ||||
this.message = message; | this.message = message; | ||||
this.lineHash = lineHash; | this.lineHash = lineHash; | ||||
this.ruleKey = ruleKey; | this.ruleKey = ruleKey; | ||||
this.status = status; | this.status = status; | ||||
this.branchName = branchName; | |||||
this.branchKey = branchKey; | |||||
this.branchType = branchType; | |||||
this.updateDate = updateDate; | this.updateDate = updateDate; | ||||
} | } | ||||
return status; | return status; | ||||
} | } | ||||
public String getBranchName() { | |||||
return branchName; | |||||
public String getBranchKey() { | |||||
return branchKey; | |||||
} | |||||
public KeyType getBranchType() { | |||||
return branchType; | |||||
} | } | ||||
@Override | @Override |
for (Map.Entry<DefaultIssue, SiblingIssue> e : matchedRaws.entrySet()) { | for (Map.Entry<DefaultIssue, SiblingIssue> e : matchedRaws.entrySet()) { | ||||
SiblingIssue issue = e.getValue(); | SiblingIssue issue = e.getValue(); | ||||
issueLifecycle.mergeConfirmedOrResolvedFromShortLivingBranch(e.getKey(), defaultIssues.get(issue), issue.getBranchName()); | |||||
issueLifecycle.mergeConfirmedOrResolvedFromShortLivingBranchOrPr(e.getKey(), defaultIssues.get(issue), issue.getBranchType(), issue.getBranchKey()); | |||||
} | } | ||||
} | } | ||||
} | } |
} | } | ||||
private static SiblingIssue toSiblingIssue(ShortBranchIssueDto dto) { | private static SiblingIssue toSiblingIssue(ShortBranchIssueDto dto) { | ||||
return new SiblingIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getBranchName(), | |||||
return new SiblingIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getBranchKey(), dto.getKeyType(), | |||||
longToDate(dto.getIssueUpdateDate())); | longToDate(dto.getIssueUpdateDate())); | ||||
} | } | ||||
import org.sonar.core.issue.DefaultIssueComment; | import org.sonar.core.issue.DefaultIssueComment; | ||||
import org.sonar.core.issue.FieldDiffs; | import org.sonar.core.issue.FieldDiffs; | ||||
import org.sonar.core.issue.IssueChangeContext; | import org.sonar.core.issue.IssueChangeContext; | ||||
import org.sonar.db.component.BranchType; | |||||
import org.sonar.db.component.KeyType; | |||||
import org.sonar.db.protobuf.DbCommons; | import org.sonar.db.protobuf.DbCommons; | ||||
import org.sonar.db.protobuf.DbIssues; | import org.sonar.db.protobuf.DbIssues; | ||||
import org.sonar.server.issue.IssueFieldsSetter; | import org.sonar.server.issue.IssueFieldsSetter; | ||||
} | } | ||||
@Test | @Test | ||||
public void mergeIssueFromShortLivingBranch() { | |||||
public void mergeIssueFromShortLivingBranchIntoLLB() { | |||||
DefaultIssue raw = new DefaultIssue() | DefaultIssue raw = new DefaultIssue() | ||||
.setKey("raw"); | .setKey("raw"); | ||||
DefaultIssue fromShort = new DefaultIssue() | DefaultIssue fromShort = new DefaultIssue() | ||||
when(branch.getName()).thenReturn("master"); | when(branch.getName()).thenReturn("master"); | ||||
analysisMetadataHolder.setBranch(branch); | analysisMetadataHolder.setBranch(branch); | ||||
underTest.mergeConfirmedOrResolvedFromShortLivingBranch(raw, fromShort, "feature/foo"); | |||||
underTest.mergeConfirmedOrResolvedFromShortLivingBranchOrPr(raw, fromShort, KeyType.BRANCH, "feature/foo"); | |||||
assertThat(raw.resolution()).isEqualTo("resolution"); | assertThat(raw.resolution()).isEqualTo("resolution"); | ||||
assertThat(raw.status()).isEqualTo("status"); | assertThat(raw.status()).isEqualTo("status"); | ||||
assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_SHORT_BRANCH).newValue()).isEqualTo("master"); | assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_SHORT_BRANCH).newValue()).isEqualTo("master"); | ||||
} | } | ||||
@Test | |||||
public void mergeIssueFromShortLivingBranchIntoPR() { | |||||
DefaultIssue raw = new DefaultIssue() | |||||
.setKey("raw"); | |||||
DefaultIssue fromShort = new DefaultIssue() | |||||
.setKey("short"); | |||||
fromShort.setResolution("resolution"); | |||||
fromShort.setStatus("status"); | |||||
Date commentDate = new Date(); | |||||
fromShort.addComment(new DefaultIssueComment() | |||||
.setIssueKey("short") | |||||
.setCreatedAt(commentDate) | |||||
.setUserUuid("user_uuid") | |||||
.setMarkdownText("A comment")); | |||||
Date diffDate = new Date(); | |||||
// file diff alone | |||||
fromShort.addChange(new FieldDiffs() | |||||
.setCreationDate(diffDate) | |||||
.setIssueKey("short") | |||||
.setUserUuid("user_uuid") | |||||
.setDiff("file", "uuidA1", "uuidB1")); | |||||
// file diff with another field | |||||
fromShort.addChange(new FieldDiffs() | |||||
.setCreationDate(diffDate) | |||||
.setIssueKey("short") | |||||
.setUserUuid("user_uuid") | |||||
.setDiff("severity", "MINOR", "MAJOR") | |||||
.setDiff("file", "uuidA2", "uuidB2")); | |||||
Branch branch = mock(Branch.class); | |||||
when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); | |||||
analysisMetadataHolder.setBranch(branch); | |||||
analysisMetadataHolder.setPullRequestKey("3"); | |||||
underTest.mergeConfirmedOrResolvedFromShortLivingBranchOrPr(raw, fromShort, KeyType.BRANCH, "feature/foo"); | |||||
assertThat(raw.resolution()).isEqualTo("resolution"); | |||||
assertThat(raw.status()).isEqualTo("status"); | |||||
assertThat(raw.defaultIssueComments()) | |||||
.extracting(DefaultIssueComment::issueKey, DefaultIssueComment::createdAt, DefaultIssueComment::userUuid, DefaultIssueComment::markdownText) | |||||
.containsOnly(tuple("raw", commentDate, "user_uuid", "A comment")); | |||||
assertThat(raw.changes()).hasSize(2); | |||||
assertThat(raw.changes().get(0).creationDate()).isEqualTo(diffDate); | |||||
assertThat(raw.changes().get(0).userUuid()).isEqualTo("user_uuid"); | |||||
assertThat(raw.changes().get(0).issueKey()).isEqualTo("raw"); | |||||
assertThat(raw.changes().get(0).diffs()).containsOnlyKeys("severity"); | |||||
assertThat(raw.changes().get(1).userUuid()).isEqualTo("default_user_uuid"); | |||||
assertThat(raw.changes().get(1).diffs()).containsOnlyKeys(IssueFieldsSetter.FROM_SHORT_BRANCH); | |||||
assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_SHORT_BRANCH).oldValue()).isEqualTo("feature/foo"); | |||||
assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_SHORT_BRANCH).newValue()).isEqualTo("#3"); | |||||
} | |||||
@Test | |||||
public void mergeIssueFromPRIntoLLB() { | |||||
DefaultIssue raw = new DefaultIssue() | |||||
.setKey("raw"); | |||||
DefaultIssue fromShort = new DefaultIssue() | |||||
.setKey("short"); | |||||
fromShort.setResolution("resolution"); | |||||
fromShort.setStatus("status"); | |||||
Date commentDate = new Date(); | |||||
fromShort.addComment(new DefaultIssueComment() | |||||
.setIssueKey("short") | |||||
.setCreatedAt(commentDate) | |||||
.setUserUuid("user_uuid") | |||||
.setMarkdownText("A comment")); | |||||
Date diffDate = new Date(); | |||||
// file diff alone | |||||
fromShort.addChange(new FieldDiffs() | |||||
.setCreationDate(diffDate) | |||||
.setIssueKey("short") | |||||
.setUserUuid("user_uuid") | |||||
.setDiff("file", "uuidA1", "uuidB1")); | |||||
// file diff with another field | |||||
fromShort.addChange(new FieldDiffs() | |||||
.setCreationDate(diffDate) | |||||
.setIssueKey("short") | |||||
.setUserUuid("user_uuid") | |||||
.setDiff("severity", "MINOR", "MAJOR") | |||||
.setDiff("file", "uuidA2", "uuidB2")); | |||||
Branch branch = mock(Branch.class); | |||||
when(branch.getName()).thenReturn("master"); | |||||
analysisMetadataHolder.setBranch(branch); | |||||
underTest.mergeConfirmedOrResolvedFromShortLivingBranchOrPr(raw, fromShort, KeyType.PULL_REQUEST, "1"); | |||||
assertThat(raw.resolution()).isEqualTo("resolution"); | |||||
assertThat(raw.status()).isEqualTo("status"); | |||||
assertThat(raw.defaultIssueComments()) | |||||
.extracting(DefaultIssueComment::issueKey, DefaultIssueComment::createdAt, DefaultIssueComment::userUuid, DefaultIssueComment::markdownText) | |||||
.containsOnly(tuple("raw", commentDate, "user_uuid", "A comment")); | |||||
assertThat(raw.changes()).hasSize(2); | |||||
assertThat(raw.changes().get(0).creationDate()).isEqualTo(diffDate); | |||||
assertThat(raw.changes().get(0).userUuid()).isEqualTo("user_uuid"); | |||||
assertThat(raw.changes().get(0).issueKey()).isEqualTo("raw"); | |||||
assertThat(raw.changes().get(0).diffs()).containsOnlyKeys("severity"); | |||||
assertThat(raw.changes().get(1).userUuid()).isEqualTo("default_user_uuid"); | |||||
assertThat(raw.changes().get(1).diffs()).containsOnlyKeys(IssueFieldsSetter.FROM_SHORT_BRANCH); | |||||
assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_SHORT_BRANCH).oldValue()).isEqualTo("#1"); | |||||
assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_SHORT_BRANCH).newValue()).isEqualTo("master"); | |||||
} | |||||
@Test | @Test | ||||
public void copiedIssue() { | public void copiedIssue() { | ||||
DefaultIssue raw = new DefaultIssue() | DefaultIssue raw = new DefaultIssue() |
import org.sonar.db.DbTester; | import org.sonar.db.DbTester; | ||||
import org.sonar.db.component.BranchType; | import org.sonar.db.component.BranchType; | ||||
import org.sonar.db.component.ComponentDto; | import org.sonar.db.component.ComponentDto; | ||||
import org.sonar.db.component.KeyType; | |||||
import org.sonar.db.issue.IssueDto; | import org.sonar.db.issue.IssueDto; | ||||
import org.sonar.db.issue.IssueTesting; | import org.sonar.db.issue.IssueTesting; | ||||
import org.sonar.db.rule.RuleDefinitionDto; | import org.sonar.db.rule.RuleDefinitionDto; | ||||
copier.tryMerge(FILE_1, Collections.singleton(newIssue)); | copier.tryMerge(FILE_1, Collections.singleton(newIssue)); | ||||
ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); | ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); | ||||
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch1")); | |||||
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranchOrPr(eq(newIssue), issueToMerge.capture(), eq(KeyType.BRANCH), eq("myBranch1")); | |||||
assertThat(issueToMerge.getValue().key()).isEqualTo("issue1"); | assertThat(issueToMerge.getValue().key()).isEqualTo("issue1"); | ||||
} | } | ||||
copier.tryMerge(FILE_1, Collections.singleton(newIssue)); | copier.tryMerge(FILE_1, Collections.singleton(newIssue)); | ||||
ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); | ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); | ||||
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch1")); | |||||
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranchOrPr(eq(newIssue), issueToMerge.capture(), eq(KeyType.BRANCH), eq("myBranch1")); | |||||
assertThat(issueToMerge.getValue().key()).isEqualTo("issue1"); | assertThat(issueToMerge.getValue().key()).isEqualTo("issue1"); | ||||
} | } | ||||
copier.tryMerge(FILE_1, Collections.singleton(newIssue)); | copier.tryMerge(FILE_1, Collections.singleton(newIssue)); | ||||
ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); | ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); | ||||
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch2")); | |||||
verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranchOrPr(eq(newIssue), issueToMerge.capture(), eq(KeyType.BRANCH), eq("myBranch2")); | |||||
assertThat(issueToMerge.getValue().key()).isEqualTo("issue2"); | assertThat(issueToMerge.getValue().key()).isEqualTo("issue2"); | ||||
assertThat(issueToMerge.getValue().defaultIssueComments()).isNotEmpty(); | assertThat(issueToMerge.getValue().defaultIssueComments()).isNotEmpty(); |
import org.apache.commons.lang.builder.ToStringBuilder; | import org.apache.commons.lang.builder.ToStringBuilder; | ||||
import org.apache.commons.lang.builder.ToStringStyle; | import org.apache.commons.lang.builder.ToStringStyle; | ||||
import org.sonar.api.rule.RuleKey; | import org.sonar.api.rule.RuleKey; | ||||
import org.sonar.db.component.KeyType; | |||||
public final class ShortBranchIssueDto implements Serializable { | public final class ShortBranchIssueDto implements Serializable { | ||||
// joins | // joins | ||||
private String ruleKey; | private String ruleKey; | ||||
private String ruleRepo; | private String ruleRepo; | ||||
private String branchName; | |||||
private String branchKey; | |||||
private KeyType keyType; | |||||
public String getKey() { | public String getKey() { | ||||
return kee; | return kee; | ||||
return this; | return this; | ||||
} | } | ||||
public String getBranchName() { | |||||
return branchName; | |||||
/** | |||||
* Branch name for SLB, PR key for PR | |||||
*/ | |||||
public String getBranchKey() { | |||||
return branchKey; | |||||
} | } | ||||
public ShortBranchIssueDto setBranchName(String s) { | |||||
this.branchName = s; | |||||
public ShortBranchIssueDto setBranchKey(String s) { | |||||
this.branchKey = s; | |||||
return this; | |||||
} | |||||
public KeyType getKeyType() { | |||||
return keyType; | |||||
} | |||||
public ShortBranchIssueDto setKeyType(KeyType s) { | |||||
this.keyType = s; | |||||
return this; | return this; | ||||
} | } | ||||
i.issue_update_date as issueUpdateDate, | i.issue_update_date as issueUpdateDate, | ||||
r.plugin_rule_key as ruleKey, | r.plugin_rule_key as ruleKey, | ||||
r.plugin_name as ruleRepo, | r.plugin_name as ruleRepo, | ||||
b.kee as branchName | |||||
b.kee as branchKey, | |||||
b.key_type as keyType | |||||
from issues i | from issues i | ||||
inner join rules r on r.id = i.rule_id | inner join rules r on r.id = i.rule_id | ||||
inner join project_branches b on i.project_uuid = b.uuid | inner join project_branches b on i.project_uuid = b.uuid |
import org.sonar.db.component.BranchType; | import org.sonar.db.component.BranchType; | ||||
import org.sonar.db.component.ComponentDto; | import org.sonar.db.component.ComponentDto; | ||||
import org.sonar.db.component.ComponentTesting; | import org.sonar.db.component.ComponentTesting; | ||||
import org.sonar.db.component.KeyType; | |||||
import org.sonar.db.organization.OrganizationDto; | import org.sonar.db.organization.OrganizationDto; | ||||
import org.sonar.db.rule.RuleDefinitionDto; | import org.sonar.db.rule.RuleDefinitionDto; | ||||
import org.sonar.db.rule.RuleDto; | import org.sonar.db.rule.RuleDto; | ||||
assertThat(fp.getChecksum()).isNotEmpty(); | assertThat(fp.getChecksum()).isNotEmpty(); | ||||
assertThat(fp.getRuleKey()).isNotNull(); | assertThat(fp.getRuleKey()).isNotNull(); | ||||
assertThat(fp.getStatus()).isNotNull(); | assertThat(fp.getStatus()).isNotNull(); | ||||
assertThat(fp.getBranchName()).isEqualTo("feature/foo"); | |||||
assertThat(fp.getBranchKey()).isEqualTo("feature/foo"); | |||||
assertThat(fp.getKeyType()).isEqualTo(KeyType.BRANCH); | |||||
assertThat(fp.getIssueUpdateDate()).isNotNull(); | assertThat(fp.getIssueUpdateDate()).isNotNull(); | ||||
} | } | ||||
issue.changelog.was=was {0} | issue.changelog.was=was {0} | ||||
issue.change.file_move=The file has been moved from {0} to {1} | issue.change.file_move=The file has been moved from {0} to {1} | ||||
issue.change.from_long_branch=The issue has been copied from branch '{0}' to branch '{1}' | issue.change.from_long_branch=The issue has been copied from branch '{0}' to branch '{1}' | ||||
issue.change.from_short_branch=The issue has been merged from branch '{0}' into branch '{1}' | |||||
issue.change.from_short_branch=The issue has been merged from '{0}' into '{1}' | |||||
issue.changelog.removed={0} removed | issue.changelog.removed={0} removed | ||||
issue.changelog.field.severity=Severity | issue.changelog.field.severity=Severity | ||||
issue.changelog.field.actionPlan=Action Plan | issue.changelog.field.actionPlan=Action Plan |