From a4c77c9e985f237777e9d1865361d2b909d5b1f4 Mon Sep 17 00:00:00 2001 From: Michal Duda Date: Thu, 1 Apr 2021 15:42:51 +0200 Subject: [PATCH] SONAR-14646 copy issue state on branch merge --- .../client/gitlab/GitlabHttpClientTest.java | 47 ++--- .../SiblingComponentsWithOpenIssues.java | 41 +++-- .../projectanalysis/issue/IssueLifecycle.java | 8 +- .../projectanalysis/issue/SiblingIssue.java | 10 +- .../issue/SiblingsIssueMerger.java | 2 +- .../issue/SiblingsIssuesLoader.java | 5 +- .../SiblingComponentsWithOpenIssuesTest.java | 4 +- .../issue/IssueLifecycleTest.java | 2 +- .../issue/SiblingsIssueMergerTest.java | 19 +- .../org/sonar/db/component/ComponentDao.java | 15 +- .../sonar/db/component/ComponentMapper.java | 6 +- .../db/newcodeperiod/NewCodePeriodDao.java | 5 + .../db/newcodeperiod/NewCodePeriodDto.java | 3 +- .../db/newcodeperiod/NewCodePeriodMapper.java | 3 + .../sonar/db/component/ComponentMapper.xml | 15 +- .../db/newcodeperiod/NewCodePeriodMapper.xml | 25 +++ server/sonar-db-dao/src/schema/schema-sq.ddl | 2 + .../sonar/db/component/ComponentDaoTest.java | 40 ++++- .../newcodeperiod/NewCodePeriodDaoTest.java | 169 +++++++----------- .../MigrationConfigurationModule.java | 2 + .../v89/AddIndicesToNewCodePeriodTable.java | 75 ++++++++ .../db/migration/version/v89/DbVersion89.java | 32 ++++ .../AddIndicesToNewCodePeriodTableTest.java | 54 ++++++ .../version/v89/DbVersion89Test.java | 42 +++++ .../schema.sql | 11 ++ 25 files changed, 472 insertions(+), 165 deletions(-) create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTable.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v89/DbVersion89.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTableTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v89/DbVersion89Test.java create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTableTest/schema.sql diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java index f5ba152946d..1a67af5818b 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java @@ -39,7 +39,6 @@ import static org.assertj.core.api.Assertions.tuple; public class GitlabHttpClientTest { - @Rule public LogTester logTester = new LogTester(); @@ -132,7 +131,7 @@ public class GitlabHttpClientTest { } @Test - public void get_branches(){ + public void get_branches() { MockResponse response = new MockResponse() .setResponseCode(200) .setBody("[{\n" @@ -219,12 +218,12 @@ public class GitlabHttpClientTest { assertThat(projectList.getProjects()).hasSize(3); assertThat(projectList.getProjects()).extracting( Project::getId, Project::getName, Project::getNameWithNamespace, Project::getPath, Project::getPathWithNamespace, Project::getWebUrl).containsExactly( - tuple(1L, "SonarQube example 1", "SonarSource / SonarQube / SonarQube example 1", "sonarqube-example-1", "sonarsource/sonarqube/sonarqube-example-1", - "https://example.gitlab.com/sonarsource/sonarqube/sonarqube-example-1"), - tuple(2L, "SonarQube example 2", "SonarSource / SonarQube / SonarQube example 2", "sonarqube-example-2", "sonarsource/sonarqube/sonarqube-example-2", - "https://example.gitlab.com/sonarsource/sonarqube/sonarqube-example-2"), - tuple(3L, "SonarQube example 3", "SonarSource / SonarQube / SonarQube example 3", "sonarqube-example-3", "sonarsource/sonarqube/sonarqube-example-3", - "https://example.gitlab.com/sonarsource/sonarqube/sonarqube-example-3")); + tuple(1L, "SonarQube example 1", "SonarSource / SonarQube / SonarQube example 1", "sonarqube-example-1", "sonarsource/sonarqube/sonarqube-example-1", + "https://example.gitlab.com/sonarsource/sonarqube/sonarqube-example-1"), + tuple(2L, "SonarQube example 2", "SonarSource / SonarQube / SonarQube example 2", "sonarqube-example-2", "sonarsource/sonarqube/sonarqube-example-2", + "https://example.gitlab.com/sonarsource/sonarqube/sonarqube-example-2"), + tuple(3L, "SonarQube example 3", "SonarSource / SonarQube / SonarQube example 3", "sonarqube-example-3", "sonarsource/sonarqube/sonarqube-example-3", + "https://example.gitlab.com/sonarsource/sonarqube/sonarqube-example-3")); RecordedRequest projectGitlabRequest = server.takeRequest(10, TimeUnit.SECONDS); String gitlabUrlCall = projectGitlabRequest.getRequestUrl().toString(); @@ -320,8 +319,8 @@ public class GitlabHttpClientTest { .isInstanceOf(IllegalArgumentException.class) .hasMessage("Could not validate GitLab read permission. Got an unexpected answer."); assertThat(logTester.logs(LoggerLevel.INFO).get(0)) - .contains("Gitlab API call to [http://localhost:"+server.getPort()+"/projects] " + - "failed with error message : [Failed to connect to localhost"); + .contains("Gitlab API call to [" + server.url("/projects") + "] " + + "failed with error message : [Failed to connect to " + server.getHostName()); } @Test @@ -332,8 +331,8 @@ public class GitlabHttpClientTest { .isInstanceOf(IllegalArgumentException.class) .hasMessage("Could not validate GitLab token. Got an unexpected answer."); assertThat(logTester.logs(LoggerLevel.INFO).get(0)) - .contains("Gitlab API call to [http://localhost:"+server.getPort()+"/user] " + - "failed with error message : [Failed to connect to localhost"); + .contains("Gitlab API call to [" + server.url("user") + "] " + + "failed with error message : [Failed to connect to " + server.getHostName()); } @Test @@ -344,8 +343,8 @@ public class GitlabHttpClientTest { .isInstanceOf(IllegalArgumentException.class) .hasMessage("Could not validate GitLab write permission. Got an unexpected answer."); assertThat(logTester.logs(LoggerLevel.INFO).get(0)) - .contains("Gitlab API call to [http://localhost:"+server.getPort()+"/markdown] " + - "failed with error message : [Failed to connect to localhost"); + .contains("Gitlab API call to [" + server.url("/markdown") + "] " + + "failed with error message : [Failed to connect to " + server.getHostName()); } @Test @@ -354,10 +353,10 @@ public class GitlabHttpClientTest { assertThatThrownBy(() -> underTest.getProject(gitlabUrl, "token", 0L)) .isInstanceOf(IllegalStateException.class) - .hasMessageContaining("Failed to connect to localhost"); + .hasMessageContaining("Failed to connect to"); assertThat(logTester.logs(LoggerLevel.INFO).get(0)) - .contains("Gitlab API call to [http://localhost:"+server.getPort()+"/projects/0] " + - "failed with error message : [Failed to connect to localhost"); + .contains("Gitlab API call to [" + server.url("/projects/0") + "] " + + "failed with error message : [Failed to connect to " + server.getHostName()); } @Test @@ -366,10 +365,10 @@ public class GitlabHttpClientTest { assertThatThrownBy(() -> underTest.getBranches(gitlabUrl, "token", 0L)) .isInstanceOf(IllegalStateException.class) - .hasMessageContaining("Failed to connect to localhost"); + .hasMessageContaining("Failed to connect to " + server.getHostName()); assertThat(logTester.logs(LoggerLevel.INFO).get(0)) - .contains("Gitlab API call to [http://localhost:"+server.getPort()+"/projects/0/repository/branches] " + - "failed with error message : [Failed to connect to localhost"); + .contains("Gitlab API call to [" + server.url("/projects/0/repository/branches") + "] " + + "failed with error message : [Failed to connect to " + server.getHostName()); } @Test @@ -378,9 +377,11 @@ public class GitlabHttpClientTest { assertThatThrownBy(() -> underTest.searchProjects(gitlabUrl, "token", null, 1, 1)) .isInstanceOf(IllegalStateException.class) - .hasMessageContaining("Failed to connect to localhost"); + .hasMessageContaining("Failed to connect to"); assertThat(logTester.logs(LoggerLevel.INFO).get(0)) - .contains("Gitlab API call to [http://localhost:"+server.getPort()+"/projects?archived=false&simple=true&membership=true&order_by=name&sort=asc&search=&page=1&per_page=1] " + - "failed with error message : [Failed to connect to localhost"); + .contains( + "Gitlab API call to [" + server.url("/projects?archived=false&simple=true&membership=true&order_by=name&sort=asc&search=&page=1&per_page=1") + + "] " + + "failed with error message : [Failed to connect to " + server.getHostName()); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssues.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssues.java index 900dc82c152..1139a1e646f 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssues.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssues.java @@ -33,7 +33,9 @@ import org.sonar.db.component.KeyWithUuidDto; import static org.sonar.db.component.ComponentDto.removeBranchAndPullRequestFromKey; /** - * Cache a map of component key -> set<uuid> in sibling PRs that have open issues + * Cache a map of component key -> set<uuid> in: + * - sibling PRs that have open issues + * - branches that use reference branch as new code period setting and have it set to currently analysed branch */ public class SiblingComponentsWithOpenIssues { private final DbClient dbClient; @@ -52,18 +54,37 @@ public class SiblingComponentsWithOpenIssues { String currentBranchUuid = treeRootHolder.getRoot().getUuid(); String referenceBranchUuid; - if (metadataHolder.isPullRequest()) { - referenceBranchUuid = metadataHolder.getBranch().getReferenceBranchUuid(); - } else { - referenceBranchUuid = currentBranchUuid; - } - uuidsByKey = new HashMap<>(); + try (DbSession dbSession = dbClient.openSession(false)) { - List components = dbClient.componentDao().selectAllSiblingComponentKeysHavingOpenIssues(dbSession, referenceBranchUuid, currentBranchUuid); - for (KeyWithUuidDto dto : components) { - uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid()); + if (metadataHolder.isPullRequest()) { + referenceBranchUuid = metadataHolder.getBranch().getReferenceBranchUuid(); + } else { + referenceBranchUuid = currentBranchUuid; + addComponentsFromBranchesThatUseCurrentBranchAsNewCodePeriodReferenceAndHaveOpenIssues(dbSession); } + + addComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues(dbSession, referenceBranchUuid, currentBranchUuid); + } + } + + private void addComponentsFromBranchesThatUseCurrentBranchAsNewCodePeriodReferenceAndHaveOpenIssues(DbSession dbSession) { + String projectUuid = metadataHolder.getProject().getUuid(); + String currentBranchName = metadataHolder.getBranch().getName(); + + Set branchUuids = dbClient.newCodePeriodDao().selectBranchesReferencing(dbSession, projectUuid, currentBranchName); + + List components = dbClient.componentDao().selectComponentsFromBranchesThatHaveOpenIssues(dbSession, branchUuids); + for (KeyWithUuidDto dto : components) { + uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid()); + } + } + + private void addComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues(DbSession dbSession, String referenceBranchUuid, String currentBranchUuid) { + List components = dbClient.componentDao().selectComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues( + dbSession, referenceBranchUuid, currentBranchUuid); + for (KeyWithUuidDto dto : components) { + uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid()); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java index b395611cdd9..5e8fb336ac0 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java @@ -31,6 +31,7 @@ import org.sonar.core.issue.DefaultIssueComment; import org.sonar.core.issue.FieldDiffs; import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.util.Uuids; +import org.sonar.db.component.BranchType; import org.sonar.server.issue.IssueFieldsSetter; import org.sonar.server.issue.workflow.IssueWorkflow; @@ -58,8 +59,7 @@ public class IssueLifecycle { this(analysisMetadataHolder, IssueChangeContext.createScan(new Date(analysisMetadataHolder.getAnalysisDate())), workflow, updater, debtCalculator, ruleRepository); } - @VisibleForTesting - IssueLifecycle(AnalysisMetadataHolder analysisMetadataHolder, IssueChangeContext changeContext, IssueWorkflow workflow, IssueFieldsSetter updater, + @VisibleForTesting IssueLifecycle(AnalysisMetadataHolder analysisMetadataHolder, IssueChangeContext changeContext, IssueWorkflow workflow, IssueFieldsSetter updater, DebtCalculator debtCalculator, RuleRepository ruleRepository) { this.analysisMetadataHolder = analysisMetadataHolder; this.workflow = workflow; @@ -102,9 +102,9 @@ public class IssueLifecycle { raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_BRANCH, branchName, analysisMetadataHolder.getBranch().getName()); } - public void mergeConfirmedOrResolvedFromPr(DefaultIssue raw, DefaultIssue base, String pr) { + public void mergeConfirmedOrResolvedFromPrOrBranch(DefaultIssue raw, DefaultIssue base, BranchType branchType, String prOrBranchKey) { copyAttributesOfIssueFromAnotherBranch(raw, base); - String from = "#" + pr; + String from = (branchType == BranchType.PULL_REQUEST) ? "#" + prOrBranchKey : prOrBranchKey; String to = analysisMetadataHolder.isPullRequest() ? ("#" + analysisMetadataHolder.getPullRequestKey()) : analysisMetadataHolder.getBranch().getName(); raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_BRANCH, from, to); } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingIssue.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingIssue.java index e39e2dab660..2a289e9d00a 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingIssue.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingIssue.java @@ -25,6 +25,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import org.sonar.api.rule.RuleKey; import org.sonar.core.issue.tracking.Trackable; +import org.sonar.db.component.BranchType; @Immutable public class SiblingIssue implements Trackable { @@ -35,9 +36,11 @@ public class SiblingIssue implements Trackable { private final RuleKey ruleKey; private final String status; private final String prKey; + private final BranchType branchType; private final Date updateDate; - SiblingIssue(String key, @Nullable Integer line, @Nullable String message, @Nullable String lineHash, RuleKey ruleKey, String status, String prKey, Date updateDate) { + SiblingIssue(String key, @Nullable Integer line, @Nullable String message, @Nullable String lineHash, RuleKey ruleKey, String status, String prKey, BranchType branchType, + Date updateDate) { this.key = key; this.line = line; this.message = message; @@ -45,6 +48,7 @@ public class SiblingIssue implements Trackable { this.ruleKey = ruleKey; this.status = status; this.prKey = prKey; + this.branchType = branchType; this.updateDate = updateDate; } @@ -64,6 +68,10 @@ public class SiblingIssue implements Trackable { return message; } + public BranchType getBranchType() { + return branchType; + } + @CheckForNull @Override public String getLineHash() { diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMerger.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMerger.java index 37ebcfff810..24e1a51d41b 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMerger.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMerger.java @@ -55,7 +55,7 @@ public class SiblingsIssueMerger { for (Map.Entry e : matchedRaws.entrySet()) { SiblingIssue issue = e.getValue(); - issueLifecycle.mergeConfirmedOrResolvedFromPr(e.getKey(), defaultIssues.get(issue), issue.getPrKey()); + issueLifecycle.mergeConfirmedOrResolvedFromPrOrBranch(e.getKey(), defaultIssues.get(issue), issue.getBranchType(), issue.getPrKey()); } } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssuesLoader.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssuesLoader.java index b70917e7452..8a2b7ddf936 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssuesLoader.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssuesLoader.java @@ -25,13 +25,11 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import org.sonar.api.utils.Preconditions; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues; import org.sonar.core.issue.DefaultIssue; import org.sonar.db.DbClient; import org.sonar.db.DbSession; -import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.issue.PrIssueDto; @@ -69,9 +67,8 @@ public class SiblingsIssuesLoader { } private static SiblingIssue toSiblingIssue(PrIssueDto dto) { - Preconditions.checkState(dto.getBranchType().equals(BranchType.PULL_REQUEST), "Expected all issues to belong to P/Rs"); return new SiblingIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getBranchKey(), - longToDate(dto.getIssueUpdateDate())); + dto.getBranchType(), longToDate(dto.getIssueUpdateDate())); } public Map loadDefaultIssuesWithChanges(Collection lightIssues) { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java index 313cd382861..7f80d971f43 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java @@ -19,6 +19,7 @@ */ package org.sonar.ce.task.projectanalysis.component; +import java.util.Collections; import javax.annotation.Nullable; import org.junit.Before; import org.junit.Rule; @@ -29,8 +30,8 @@ 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 org.sonar.server.project.Project; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -64,6 +65,7 @@ public class SiblingComponentsWithOpenIssuesTest { @Before public void setUp() { ComponentDto project = db.components().insertPublicProject(); + metadataHolder.setProject(new Project(project.uuid(), project.getKey(), project.name(), project.description(), Collections.emptyList())); branch1 = db.components().insertProjectBranch(project, b -> b.setKey("branch1"), b -> b.setBranchType(BranchType.BRANCH)); branch1pr1 = db.components().insertProjectBranch(project, diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java index 384ea59093b..913dc740343 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java @@ -145,7 +145,7 @@ public class IssueLifecycleTest { when(branch.getName()).thenReturn("master"); analysisMetadataHolder.setBranch(branch); - underTest.mergeConfirmedOrResolvedFromPr(raw, fromShort, "2"); + underTest.mergeConfirmedOrResolvedFromPrOrBranch(raw, fromShort, BranchType.PULL_REQUEST, "2"); assertThat(raw.resolution()).isEqualTo("resolution"); assertThat(raw.status()).isEqualTo("status"); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java index 579735a4644..35d228e1faf 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java @@ -27,8 +27,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.sonar.api.config.internal.MapSettings; import org.sonar.api.issue.Issue; import org.sonar.api.rule.RuleKey; @@ -48,9 +46,11 @@ import org.sonar.db.issue.IssueDto; import org.sonar.db.issue.IssueTesting; import org.sonar.db.rule.RuleDefinitionDto; import org.sonar.db.user.UserDto; +import org.sonar.server.project.Project; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -58,11 +58,8 @@ import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builde import static org.sonar.db.component.ComponentTesting.newFileDto; public class SiblingsIssueMergerTest { - @Mock - private IssueLifecycle issueLifecycle; - - @Mock - private Branch branch; + private final IssueLifecycle issueLifecycle = mock(IssueLifecycle.class); + private final Branch branch = mock(Branch.class); @Rule public DbTester db = DbTester.create(); @@ -102,7 +99,6 @@ public class SiblingsIssueMergerTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); DbClient dbClient = db.getDbClient(); ComponentIssuesLoader componentIssuesLoader = new ComponentIssuesLoader(dbClient, null, null, new MapSettings().asConfig(), System2.INSTANCE); copier = new SiblingsIssueMerger(new SiblingsIssuesLoader(new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, dbClient), dbClient, componentIssuesLoader), @@ -124,6 +120,7 @@ public class SiblingsIssueMergerTest { rule = db.rules().insert(); when(branch.getReferenceBranchUuid()).thenReturn(projectDto.uuid()); metadataHolder.setBranch(branch); + metadataHolder.setProject(new Project(projectDto.uuid(), projectDto.getKey(), projectDto.name(), projectDto.description(), Collections.emptyList())); } @Test @@ -150,7 +147,7 @@ public class SiblingsIssueMergerTest { copier.tryMerge(FILE_1, Collections.singleton(newIssue)); ArgumentCaptor issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); - verify(issueLifecycle).mergeConfirmedOrResolvedFromPr(eq(newIssue), issueToMerge.capture(), eq("myBranch1")); + verify(issueLifecycle).mergeConfirmedOrResolvedFromPrOrBranch(eq(newIssue), issueToMerge.capture(), eq(BranchType.PULL_REQUEST), eq("myBranch1")); assertThat(issueToMerge.getValue().key()).isEqualTo("issue1"); } @@ -169,7 +166,7 @@ public class SiblingsIssueMergerTest { copier.tryMerge(FILE_1, Collections.singleton(newIssue)); ArgumentCaptor issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); - verify(issueLifecycle).mergeConfirmedOrResolvedFromPr(eq(newIssue), issueToMerge.capture(), eq("myBranch1")); + verify(issueLifecycle).mergeConfirmedOrResolvedFromPrOrBranch(eq(newIssue), issueToMerge.capture(), eq(BranchType.PULL_REQUEST), eq("myBranch1")); assertThat(issueToMerge.getValue().key()).isEqualTo("issue1"); } @@ -186,7 +183,7 @@ public class SiblingsIssueMergerTest { copier.tryMerge(FILE_1, Collections.singleton(newIssue)); ArgumentCaptor issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class); - verify(issueLifecycle).mergeConfirmedOrResolvedFromPr(eq(newIssue), issueToMerge.capture(), eq("myBranch2")); + verify(issueLifecycle).mergeConfirmedOrResolvedFromPrOrBranch(eq(newIssue), issueToMerge.capture(), eq(BranchType.PULL_REQUEST), eq("myBranch2")); assertThat(issueToMerge.getValue().key()).isEqualTo("issue"); assertThat(issueToMerge.getValue().defaultIssueComments()).isNotEmpty(); 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 5495ad09c36..7a1b2b8ecaa 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 @@ -302,8 +302,19 @@ public class ComponentDao implements Dao { * Returns components with open issues from P/Rs that use a certain branch as reference (reference branch). * Excludes components from the current branch. */ - public List selectAllSiblingComponentKeysHavingOpenIssues(DbSession dbSession, String referenceBranchUuid, String currentBranchUuid) { - return mapper(dbSession).selectAllSiblingComponentKeysHavingOpenIssues(referenceBranchUuid, currentBranchUuid); + public List selectComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues(DbSession dbSession, String referenceBranchUuid, String currentBranchUuid) { + return mapper(dbSession).selectComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues(referenceBranchUuid, currentBranchUuid); + } + + /** + * Returns components with open issues from the given branches + */ + public List selectComponentsFromBranchesThatHaveOpenIssues(DbSession dbSession, Set branchUuids) { + if (branchUuids.isEmpty()) { + return emptyList(); + } + + return executeLargeInputs(branchUuids, input -> mapper(dbSession).selectComponentsFromBranchesThatHaveOpenIssues(input)); } /** 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 aea46f11177..6521caae219 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 @@ -138,8 +138,10 @@ public interface ComponentMapper { void delete(String componentUuid); - List selectAllSiblingComponentKeysHavingOpenIssues(@Param("referenceBranchUuid") String referenceBranchUuid, - @Param("currentBranchUuid") String currentBranchUuid); + List selectComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues(@Param("referenceBranchUuid") String referenceBranchUuid, + @Param("currentBranchUuid") String currentBranchUuid); + + List selectComponentsFromBranchesThatHaveOpenIssues(@Param("branchUuids") List branchUuids); List selectPrivateProjectsWithNcloc(); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java index 8a04c06aac3..8014858baaf 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java @@ -21,6 +21,7 @@ package org.sonar.db.newcodeperiod; import java.util.List; import java.util.Optional; +import java.util.Set; import javax.annotation.Nullable; import org.sonar.api.utils.System2; import org.sonar.core.util.UuidFactory; @@ -89,6 +90,10 @@ public class NewCodePeriodDao implements Dao { return ofNullable(mapper(dbSession).selectByBranch(projectUuid, branchUuid)); } + public Set selectBranchesReferencing(DbSession dbSession, String projectUuid, String referenceBranchName) { + return mapper(dbSession).selectBranchesReferencing(projectUuid, referenceBranchName); + } + public boolean existsByProjectAnalysisUuid(DbSession dbSession, String projectAnalysisUuid) { requireNonNull(projectAnalysisUuid, MSG_PROJECT_UUID_NOT_SPECIFIED); return mapper(dbSession).countByProjectAnalysis(projectAnalysisUuid) > 0; diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java index 128d8a4e840..b3220f8ace2 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java @@ -91,11 +91,12 @@ public class NewCodePeriodDto { return this; } + @CheckForNull public String getValue() { return value; } - public NewCodePeriodDto setValue(String value) { + public NewCodePeriodDto setValue(@Nullable String value) { this.value = value; return this; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java index af00d869f26..5f28f8f0d86 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java @@ -21,6 +21,7 @@ package org.sonar.db.newcodeperiod; import java.util.List; import java.util.Optional; +import java.util.Set; import org.apache.ibatis.annotations.Param; public interface NewCodePeriodMapper { @@ -39,6 +40,8 @@ public interface NewCodePeriodMapper { NewCodePeriodDto selectByBranch(@Param("projectUuid") String projectUuid, @Param("branchUuid") String branchUuid); + Set selectBranchesReferencing(@Param("projectUuid") String projectUuid, @Param("referenceBranchName") String referenceBranchName); + long countByProjectAnalysis(String projectAnalysisUuid); List selectAllByProject(String projectUuid); 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 7e9e3d814b0..87d255caefb 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 @@ -678,7 +678,7 @@ DELETE FROM components WHERE uuid=#{componentUuid,jdbcType=VARCHAR} - SELECT DISTINCT p.kee as kee, p.uuid as uuid FROM components p JOIN issues i ON p.uuid = i.component_uuid @@ -690,6 +690,19 @@ AND i.status != 'CLOSED' + + + + SELECT ncp.branch_uuid + FROM new_code_periods ncp + WHERE + ncp.branch_uuid IS NOT NULL + AND ncp.project_uuid = #{projectUuid, jdbcType=VARCHAR} + AND ncp.type='REFERENCE_BRANCH' + AND ncp.value=#{referenceBranchName, jdbcType=VARCHAR} + UNION + + + SELECT pb.uuid + FROM project_branches pb, new_code_periods ncp1 + WHERE + ncp1.project_uuid = pb.project_uuid + AND ncp1.branch_uuid IS NULL + AND ncp1.project_uuid = #{projectUuid, jdbcType=VARCHAR} + AND ncp1.type='REFERENCE_BRANCH' + AND ncp1.value=#{referenceBranchName, jdbcType=VARCHAR} + AND NOT EXISTS (select ncp2.value from new_code_periods ncp2 where ncp2.branch_uuid = pb.uuid) + AND pb.kee != #{referenceBranchName, jdbcType=VARCHAR} + + +