]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14646 copy issue state on branch merge
authorMichal Duda <michal.duda@sonarsource.com>
Thu, 1 Apr 2021 13:42:51 +0000 (15:42 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 9 Apr 2021 20:03:53 +0000 (20:03 +0000)
25 files changed:
server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssues.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingIssue.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMerger.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssuesLoader.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/newcodeperiod/NewCodePeriodMapper.xml
server/sonar-db-dao/src/schema/schema-sq.ddl
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/newcodeperiod/NewCodePeriodDaoTest.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v89/DbVersion89.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v89/DbVersion89Test.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTableTest/schema.sql [new file with mode: 0644]

index f5ba152946daafdfe67cda4162a710ea7cae1c30..1a67af5818b5ab74cead169df7792cc0693ee6d9 100644 (file)
@@ -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());
   }
 }
index 900dc82c152c3d3d8348d472539dce87736bded7..1139a1e646f3857a04eb65f19970ecdc1396a13a 100644 (file)
@@ -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&lt;uuid&gt; in sibling PRs that have open issues
+ * Cache a map of component key -> set&lt;uuid&gt; 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<KeyWithUuidDto> 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<String> branchUuids = dbClient.newCodePeriodDao().selectBranchesReferencing(dbSession, projectUuid, currentBranchName);
+
+    List<KeyWithUuidDto> 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<KeyWithUuidDto> components = dbClient.componentDao().selectComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues(
+      dbSession, referenceBranchUuid, currentBranchUuid);
+    for (KeyWithUuidDto dto : components) {
+      uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid());
     }
   }
 
index b395611cdd9fa36726f1cd72e8b6179fe7725609..5e8fb336ac0a7965f6c57a24c869f56d0bbd2539 100644 (file)
@@ -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);
   }
index e39e2dab66053b99ae7f7a9eb14f0501cede08cb..2a289e9d00af14a94a204559f9e2f421b7d3b1a8 100644 (file)
@@ -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() {
index 37ebcfff810deb12a108f59a02c79785614ad9af..24e1a51d41ba842b215981868bb5ae5464bd7744 100644 (file)
@@ -55,7 +55,7 @@ public class SiblingsIssueMerger {
 
     for (Map.Entry<DefaultIssue, SiblingIssue> 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());
     }
   }
 }
index b70917e74520e9c7978e9425c58ec26c10d57f83..8a2b7ddf936835310d8bd0cc6c6d23c59b2d5304 100644 (file)
@@ -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<SiblingIssue, DefaultIssue> loadDefaultIssuesWithChanges(Collection<SiblingIssue> lightIssues) {
index 313cd38286141c1a4d6e72f103c3c85e364b5db1..7f80d971f43b878a6c40116194f667a26b2d4e61 100644 (file)
@@ -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,
index 384ea59093be6214d911f01d64741d0efc86d33d..913dc740343b72fd153ea3d80f2bdfa943e86447 100644 (file)
@@ -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");
index 579735a4644a8950ad5316243ae1514551e4d719..35d228e1fafbb4131a7dd8bc8f13c35618f95e85 100644 (file)
@@ -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<DefaultIssue> 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<DefaultIssue> 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<DefaultIssue> 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();
index 5495ad09c365f67f125ccabf4846d8e96fa9f083..7a1b2b8ecaa7dc5ee6941e3bdc54dbc85260d168 100644 (file)
@@ -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<KeyWithUuidDto> selectAllSiblingComponentKeysHavingOpenIssues(DbSession dbSession, String referenceBranchUuid, String currentBranchUuid) {
-    return mapper(dbSession).selectAllSiblingComponentKeysHavingOpenIssues(referenceBranchUuid, currentBranchUuid);
+  public List<KeyWithUuidDto> selectComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues(DbSession dbSession, String referenceBranchUuid, String currentBranchUuid) {
+    return mapper(dbSession).selectComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues(referenceBranchUuid, currentBranchUuid);
+  }
+
+  /**
+   * Returns components with open issues from the given branches
+   */
+  public List<KeyWithUuidDto> selectComponentsFromBranchesThatHaveOpenIssues(DbSession dbSession, Set<String> branchUuids) {
+    if (branchUuids.isEmpty()) {
+      return emptyList();
+    }
+
+    return executeLargeInputs(branchUuids, input -> mapper(dbSession).selectComponentsFromBranchesThatHaveOpenIssues(input));
   }
 
   /**
index aea46f11177d4a481cc2dcfdefe4fac35c731253..6521caae219fe2f24e575eb1dd3a9915d41e61ae 100644 (file)
@@ -138,8 +138,10 @@ public interface ComponentMapper {
 
   void delete(String componentUuid);
 
-  List<KeyWithUuidDto> selectAllSiblingComponentKeysHavingOpenIssues(@Param("referenceBranchUuid") String referenceBranchUuid,
-    @Param("currentBranchUuid") String currentBranchUuid);
+  List<KeyWithUuidDto> selectComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues(@Param("referenceBranchUuid") String referenceBranchUuid,
+                                                                                                @Param("currentBranchUuid") String currentBranchUuid);
+
+  List<KeyWithUuidDto> selectComponentsFromBranchesThatHaveOpenIssues(@Param("branchUuids") List<String> branchUuids);
 
   List<ProjectNclocDistributionDto> selectPrivateProjectsWithNcloc();
 
index 8a04c06aac377449ac3da5d8669fff1bc9f04681..8014858baaf80640de763acd3407e1ee74b0607c 100644 (file)
@@ -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<String> 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;
index 128d8a4e840ee33c1b9074449b8255fdf3f506e4..b3220f8ace244e86f3f1286f94a909f027927b4c 100644 (file)
@@ -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;
   }
index af00d869f26828e6865070f5a0e86adb7c94866b..5f28f8f0d86c1119d522e902f0b93b0f125edac3 100644 (file)
@@ -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<String> selectBranchesReferencing(@Param("projectUuid") String projectUuid, @Param("referenceBranchName") String referenceBranchName);
+
   long countByProjectAnalysis(String projectAnalysisUuid);
 
   List<NewCodePeriodDto> selectAllByProject(String projectUuid);
index 7e9e3d814b0b9987201723dff97df9ae3d77db4e..87d255caefb31a9fb6c85cb663ef71d140933e63 100644 (file)
     DELETE FROM components WHERE uuid=#{componentUuid,jdbcType=VARCHAR}
   </delete>
 
-  <select id="selectAllSiblingComponentKeysHavingOpenIssues" resultType="KeyWithUuid">
+  <select id="selectComponentsFromPullRequestsTargetingCurrentBranchThatHaveOpenIssues" resultType="KeyWithUuid">
     SELECT DISTINCT p.kee as kee, p.uuid as uuid FROM components p
     JOIN issues i
       ON p.uuid = i.component_uuid
       AND i.status != 'CLOSED'
   </select>
 
+  <select id="selectComponentsFromBranchesThatHaveOpenIssues" resultType="KeyWithUuid">
+    SELECT DISTINCT p.kee as kee, p.uuid as uuid
+    FROM components p
+    JOIN issues i
+      ON p.uuid = i.component_uuid
+    JOIN project_branches b
+      ON i.project_uuid = b.uuid
+      AND b.uuid in <foreach collection="branchUuids" open="(" close=")" item="branchUuid" separator=",">
+      #{branchUuid,jdbcType=VARCHAR}
+      </foreach>
+      AND i.status != 'CLOSED'
+  </select>
+
   <select id="selectPrivateProjectsWithNcloc" resultType="org.sonar.db.component.ProjectNclocDistributionDto">
     select p.kee as kee, p.name as name, max(lm.value) as ncloc
     from live_measures lm
index 8cb4148b2e46147f6858ab3f88cd5cfb004bff8d..64bc8513c3b3435788920150eeeb8e325295f14a 100644 (file)
     </choose>
   </update>
 
+  <select id="selectBranchesReferencing" parameterType="map" resultType="string">
+    <!-- branch setting -->
+    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
+
+    <!-- project default setting-->
+    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}
+  </select>
+
+
   <select id="selectByProject" parameterType="map" resultType="org.sonar.db.newcodeperiod.NewCodePeriodDto">
     SELECT
     <include refid="newCodePeriodMapperColumns"/>
index 55bc6dbb64ef2cc5e11f8183d731667c4812d24a..6a5712939d0f5edbfe9a10b5a75f47b4e9b04c98 100644 (file)
@@ -490,6 +490,8 @@ CREATE TABLE "NEW_CODE_PERIODS"(
 );
 ALTER TABLE "NEW_CODE_PERIODS" ADD CONSTRAINT "PK_NEW_CODE_PERIODS" PRIMARY KEY("UUID");
 CREATE UNIQUE INDEX "UNIQ_NEW_CODE_PERIODS" ON "NEW_CODE_PERIODS"("PROJECT_UUID", "BRANCH_UUID");
+CREATE INDEX "IDX_NCP_TYPE" ON "NEW_CODE_PERIODS"("TYPE");
+CREATE INDEX "IDX_NCP_VALUE" ON "NEW_CODE_PERIODS"("VALUE");
 
 CREATE TABLE "NOTIFICATIONS"(
     "DATA" BLOB,
index 3d0361eba7910e333181d0850f2262b8abd00ed3..baae16d1a8551d428c312f5d9913932353cbadae 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.db.component;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
@@ -49,7 +50,10 @@ import org.sonar.api.utils.System2;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.RowNotFoundException;
+import org.sonar.db.issue.IssueDto;
 import org.sonar.db.metric.MetricDto;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.source.FileSourceDto;
 
 import static com.google.common.collect.ImmutableSet.of;
@@ -65,6 +69,9 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.Assertions.entry;
 import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.issue.Issue.STATUS_CLOSED;
+import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
 import static org.sonar.api.resources.Qualifiers.APP;
 import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.resources.Qualifiers.SUBVIEW;
@@ -1799,6 +1806,38 @@ public class ComponentDaoTest {
     assertThat(underTest.existAnyOfComponentsWithQualifiers(db.getSession(), newHashSet(projectDto.getKey(), view.getKey()), newHashSet(APP, VIEW, SUBVIEW))).isTrue();
   }
 
+  @Test
+  public void selectComponentsFromBranchesThatHaveOpenIssues() {
+    final ProjectDto project = db.components().insertPrivateProjectDto(b -> b.setName("foo"));
+
+    ComponentDto branch1 = db.components().insertProjectBranch(project, ComponentTesting.newBranchDto(project.getUuid(), BRANCH).setKey("branch1"));
+    ComponentDto fileBranch1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1));
+
+    ComponentDto branch2 = db.components().insertProjectBranch(project, ComponentTesting.newBranchDto(project.getUuid(), BRANCH).setKey("branch2"));
+    ComponentDto fileBranch2 = db.components().insertComponent(ComponentTesting.newFileDto(branch2));
+    RuleDefinitionDto rule = db.rules().insert();
+    db.issues().insert(new IssueDto().setKee("i1").setComponent(fileBranch1).setProject(branch1).setRule(rule).setStatus(STATUS_CONFIRMED));
+    db.issues().insert(new IssueDto().setKee("i2").setComponent(fileBranch2).setProject(branch2).setRule(rule).setStatus(STATUS_CLOSED));
+    db.issues().insert(new IssueDto().setKee("i3").setComponent(fileBranch2).setProject(branch2).setRule(rule).setStatus(STATUS_OPEN));
+
+    List<KeyWithUuidDto> result = underTest.selectComponentsFromBranchesThatHaveOpenIssues(db.getSession(), of(branch1.uuid(), branch2.uuid()));
+
+    assertThat(result).extracting(KeyWithUuidDto::uuid).contains(fileBranch2.uuid());
+  }
+
+  @Test
+  public void selectComponentsFromBranchesThatHaveOpenIssues_returns_nothing_if_no_open_issues_in_sibling_branches() {
+    final ProjectDto project = db.components().insertPrivateProjectDto(b -> b.setName("foo"));
+    ComponentDto branch1 = db.components().insertProjectBranch(project, ComponentTesting.newBranchDto(project.getUuid(), BRANCH).setKey("branch1"));
+    ComponentDto fileBranch1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1));
+    RuleDefinitionDto rule = db.rules().insert();
+    db.issues().insert(new IssueDto().setKee("i").setComponent(fileBranch1).setProject(branch1).setRule(rule).setStatus(STATUS_CLOSED));
+
+    List<KeyWithUuidDto> result = underTest.selectComponentsFromBranchesThatHaveOpenIssues(db.getSession(), singleton(branch1.uuid()));
+
+    assertThat(result).isEmpty();
+  }
+
   private boolean privateFlagOfUuid(String uuid) {
     return underTest.selectByUuid(db.getSession(), uuid).get().isPrivate();
   }
@@ -1818,5 +1857,4 @@ public class ComponentDaoTest {
     return t -> {
     };
   }
-
 }
index 4ed29d60d88d3990714359d594151c0e69fb0dc8..1a3cac64eb45f1474d206f0066e51d3b83a30368 100644 (file)
 package org.sonar.db.newcodeperiod;
 
 import java.util.Optional;
+import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.utils.System2;
+import org.sonar.core.util.SequenceUuidFactory;
 import org.sonar.core.util.UuidFactory;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.project.ProjectDto;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS;
 
 public class NewCodePeriodDaoTest {
 
@@ -38,30 +44,23 @@ public class NewCodePeriodDaoTest {
   public DbTester db = DbTester.create(System2.INSTANCE);
 
   private final DbSession dbSession = db.getSession();
-  private final UuidFactory uuidFactory = mock(UuidFactory.class);
+  private final UuidFactory uuidFactory = new SequenceUuidFactory();
   private final NewCodePeriodDao underTest = new NewCodePeriodDao(System2.INSTANCE, uuidFactory);
-
-  private static final String NEW_CODE_PERIOD_UUID = "ncp-uuid-1";
-
+  
   @Test
   public void insert_new_code_period() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
-    underTest.insert(dbSession, new NewCodePeriodDto()
-      .setProjectUuid("proj-uuid")
-      .setBranchUuid("branch-uuid")
-      .setType(NewCodePeriodType.NUMBER_OF_DAYS)
-      .setValue("5"));
+    insert("proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5");
 
-    Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, NEW_CODE_PERIOD_UUID);
+    Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, "1");
 
     assertThat(resultOpt).isNotNull()
       .isNotEmpty();
 
     NewCodePeriodDto result = resultOpt.get();
-    assertThat(result.getUuid()).isEqualTo(NEW_CODE_PERIOD_UUID);
+    assertThat(result.getUuid()).isEqualTo("1");
     assertThat(result.getProjectUuid()).isEqualTo("proj-uuid");
     assertThat(result.getBranchUuid()).isEqualTo("branch-uuid");
-    assertThat(result.getType()).isEqualTo(NewCodePeriodType.NUMBER_OF_DAYS);
+    assertThat(result.getType()).isEqualTo(NUMBER_OF_DAYS);
     assertThat(result.getValue()).isEqualTo("5");
     assertThat(result.getCreatedAt()).isNotZero();
     assertThat(result.getUpdatedAt()).isNotZero();
@@ -77,31 +76,25 @@ public class NewCodePeriodDaoTest {
 
   @Test
   public void update_new_code_period() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
-
-    underTest.insert(dbSession, new NewCodePeriodDto()
-      .setProjectUuid("proj-uuid")
-      .setBranchUuid("branch-uuid")
-      .setType(NewCodePeriodType.NUMBER_OF_DAYS)
-      .setValue("5"));
+    insert("proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5");
 
     underTest.update(dbSession, new NewCodePeriodDto()
-      .setUuid(NEW_CODE_PERIOD_UUID)
-      .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
+      .setUuid("1")
+      .setType(SPECIFIC_ANALYSIS)
       .setProjectUuid("proj-uuid")
       .setBranchUuid("branch-uuid")
       .setValue("analysis-uuid"));
 
-    Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, NEW_CODE_PERIOD_UUID);
+    Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, "1");
 
     assertThat(resultOpt).isNotNull()
       .isNotEmpty();
 
     NewCodePeriodDto result = resultOpt.get();
-    assertThat(result.getUuid()).isEqualTo(NEW_CODE_PERIOD_UUID);
+    assertThat(result.getUuid()).isEqualTo("1");
     assertThat(result.getProjectUuid()).isEqualTo("proj-uuid");
     assertThat(result.getBranchUuid()).isEqualTo("branch-uuid");
-    assertThat(result.getType()).isEqualTo(NewCodePeriodType.SPECIFIC_ANALYSIS);
+    assertThat(result.getType()).isEqualTo(SPECIFIC_ANALYSIS);
     assertThat(result.getValue()).isEqualTo("analysis-uuid");
     assertThat(result.getCreatedAt()).isNotZero();
     assertThat(result.getUpdatedAt()).isNotZero();
@@ -112,26 +105,19 @@ public class NewCodePeriodDaoTest {
 
   @Test
   public void insert_with_upsert() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
-
-    underTest.upsert(dbSession, new NewCodePeriodDto()
-      .setUuid(NEW_CODE_PERIOD_UUID)
-      .setProjectUuid("proj-uuid")
-      .setBranchUuid("branch-uuid")
-      .setType(NewCodePeriodType.NUMBER_OF_DAYS)
-      .setValue("5"));
+    insert("proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5");
 
-    Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, NEW_CODE_PERIOD_UUID);
+    Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, "1");
 
     assertThat(resultOpt)
       .isNotNull()
       .isNotEmpty();
 
     NewCodePeriodDto result = resultOpt.get();
-    assertThat(result.getUuid()).isEqualTo(NEW_CODE_PERIOD_UUID);
+    assertThat(result.getUuid()).isEqualTo("1");
     assertThat(result.getProjectUuid()).isEqualTo("proj-uuid");
     assertThat(result.getBranchUuid()).isEqualTo("branch-uuid");
-    assertThat(result.getType()).isEqualTo(NewCodePeriodType.NUMBER_OF_DAYS);
+    assertThat(result.getType()).isEqualTo(NUMBER_OF_DAYS);
     assertThat(result.getValue()).isEqualTo("5");
     assertThat(result.getCreatedAt()).isNotZero();
     assertThat(result.getUpdatedAt()).isNotZero();
@@ -142,32 +128,26 @@ public class NewCodePeriodDaoTest {
 
   @Test
   public void update_with_upsert() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
-
-    underTest.insert(dbSession, new NewCodePeriodDto()
-      .setProjectUuid("proj-uuid")
-      .setBranchUuid("branch-uuid")
-      .setType(NewCodePeriodType.NUMBER_OF_DAYS)
-      .setValue("5"));
+    insert("proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5");
 
     underTest.upsert(dbSession, new NewCodePeriodDto()
-      .setUuid(NEW_CODE_PERIOD_UUID)
+      .setUuid("1")
       .setProjectUuid("proj-uuid")
       .setBranchUuid("branch-uuid")
-      .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
+      .setType(SPECIFIC_ANALYSIS)
       .setValue("analysis-uuid"));
 
-    Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, NEW_CODE_PERIOD_UUID);
+    Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, "1");
 
     assertThat(resultOpt)
       .isNotNull()
       .isNotEmpty();
 
     NewCodePeriodDto result = resultOpt.get();
-    assertThat(result.getUuid()).isEqualTo(NEW_CODE_PERIOD_UUID);
+    assertThat(result.getUuid()).isEqualTo("1");
     assertThat(result.getProjectUuid()).isEqualTo("proj-uuid");
     assertThat(result.getBranchUuid()).isEqualTo("branch-uuid");
-    assertThat(result.getType()).isEqualTo(NewCodePeriodType.SPECIFIC_ANALYSIS);
+    assertThat(result.getType()).isEqualTo(SPECIFIC_ANALYSIS);
     assertThat(result.getValue()).isEqualTo("analysis-uuid");
     assertThat(result.getCreatedAt()).isNotZero();
     assertThat(result.getUpdatedAt()).isNotZero();
@@ -178,13 +158,7 @@ public class NewCodePeriodDaoTest {
 
   @Test
   public void select_by_project_and_branch_uuids() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
-
-    underTest.insert(dbSession, new NewCodePeriodDto()
-      .setProjectUuid("proj-uuid")
-      .setBranchUuid("branch-uuid")
-      .setType(NewCodePeriodType.NUMBER_OF_DAYS)
-      .setValue("5"));
+    insert("proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5");
 
     Optional<NewCodePeriodDto> resultOpt = underTest.selectByBranch(dbSession, "proj-uuid", "branch-uuid");
     assertThat(resultOpt)
@@ -192,24 +166,34 @@ public class NewCodePeriodDaoTest {
       .isNotEmpty();
 
     NewCodePeriodDto result = resultOpt.get();
-    assertThat(result.getUuid()).isEqualTo(NEW_CODE_PERIOD_UUID);
+    assertThat(result.getUuid()).isEqualTo("1");
     assertThat(result.getProjectUuid()).isEqualTo("proj-uuid");
     assertThat(result.getBranchUuid()).isEqualTo("branch-uuid");
-    assertThat(result.getType()).isEqualTo(NewCodePeriodType.NUMBER_OF_DAYS);
+    assertThat(result.getType()).isEqualTo(NUMBER_OF_DAYS);
     assertThat(result.getValue()).isEqualTo("5");
     assertThat(result.getCreatedAt()).isNotZero();
     assertThat(result.getUpdatedAt()).isNotZero();
   }
 
   @Test
-  public void select_by_project_uuid() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
+  public void select_branches_referencing() {
+    ProjectDto project = db.components().insertPrivateProjectDto();
+    BranchDto mainBranch = db.getDbClient().branchDao().selectByUuid(dbSession, project.getUuid()).get();
+    BranchDto branch1 = db.components().insertProjectBranch(project);
+    BranchDto branch2 = db.components().insertProjectBranch(project);
+    BranchDto branch3 = db.components().insertProjectBranch(project);
+
+    insert(project.getUuid(), null, REFERENCE_BRANCH, mainBranch.getKey());
+    insert(project.getUuid(), branch1.getUuid(), REFERENCE_BRANCH,  mainBranch.getKey());
+    insert(project.getUuid(), branch2.getUuid(), NUMBER_OF_DAYS, "5");
+    insert(project.getUuid(), project.getUuid(), PREVIOUS_VERSION, null);
+    db.commit();
+    assertThat(underTest.selectBranchesReferencing(dbSession, project.getUuid(), mainBranch.getKey())).containsOnly(branch1.getUuid(), branch3.getUuid());
+  }
 
-    underTest.insert(dbSession, new NewCodePeriodDto()
-      .setProjectUuid("proj-uuid")
-      .setBranchUuid(null)
-      .setType(NewCodePeriodType.NUMBER_OF_DAYS)
-      .setValue("5"));
+  @Test
+  public void select_by_project_uuid() {
+    insert("proj-uuid", null, NUMBER_OF_DAYS, "5");
 
     Optional<NewCodePeriodDto> resultOpt = underTest.selectByProject(dbSession, "proj-uuid");
     assertThat(resultOpt)
@@ -217,10 +201,10 @@ public class NewCodePeriodDaoTest {
       .isNotEmpty();
 
     NewCodePeriodDto result = resultOpt.get();
-    assertThat(result.getUuid()).isEqualTo(NEW_CODE_PERIOD_UUID);
+    assertThat(result.getUuid()).isEqualTo("1");
     assertThat(result.getProjectUuid()).isEqualTo("proj-uuid");
     assertThat(result.getBranchUuid()).isNull();
-    assertThat(result.getType()).isEqualTo(NewCodePeriodType.NUMBER_OF_DAYS);
+    assertThat(result.getType()).isEqualTo(NUMBER_OF_DAYS);
     assertThat(result.getValue()).isEqualTo("5");
     assertThat(result.getCreatedAt()).isNotZero();
     assertThat(result.getUpdatedAt()).isNotZero();
@@ -228,22 +212,16 @@ public class NewCodePeriodDaoTest {
 
   @Test
   public void select_global() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
-
-    underTest.insert(dbSession, new NewCodePeriodDto()
-      .setProjectUuid(null)
-      .setBranchUuid(null)
-      .setType(NewCodePeriodType.NUMBER_OF_DAYS)
-      .setValue("30"));
+    insert(null, null, NUMBER_OF_DAYS, "30");
 
     Optional<NewCodePeriodDto> newCodePeriodDto = underTest.selectGlobal(dbSession);
     assertThat(newCodePeriodDto).isNotEmpty();
 
     NewCodePeriodDto result = newCodePeriodDto.get();
-    assertThat(result.getUuid()).isEqualTo(NEW_CODE_PERIOD_UUID);
+    assertThat(result.getUuid()).isEqualTo("1");
     assertThat(result.getProjectUuid()).isNull();
     assertThat(result.getBranchUuid()).isNull();
-    assertThat(result.getType()).isEqualTo(NewCodePeriodType.NUMBER_OF_DAYS);
+    assertThat(result.getType()).isEqualTo(NUMBER_OF_DAYS);
     assertThat(result.getValue()).isEqualTo("30");
     assertThat(result.getCreatedAt()).isNotZero();
     assertThat(result.getUpdatedAt()).isNotZero();
@@ -251,13 +229,7 @@ public class NewCodePeriodDaoTest {
 
   @Test
   public void exists_by_project_analysis_is_true() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
-
-    underTest.insert(dbSession, new NewCodePeriodDto()
-      .setProjectUuid("proj-uuid")
-      .setBranchUuid("branch-uuid")
-      .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
-      .setValue("analysis-uuid"));
+    insert("proj-uuid", "branch-uuid", SPECIFIC_ANALYSIS, "analysis-uuid");
 
     boolean exists = underTest.existsByProjectAnalysisUuid(dbSession, "analysis-uuid");
     assertThat(exists).isTrue();
@@ -265,13 +237,7 @@ public class NewCodePeriodDaoTest {
 
   @Test
   public void delete_by_project_uuid_and_branch_uuid() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
-
-    underTest.insert(dbSession, new NewCodePeriodDto()
-      .setProjectUuid("proj-uuid")
-      .setBranchUuid("branch-uuid")
-      .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
-      .setValue("analysis-uuid"));
+    insert("proj-uuid", "branch-uuid", SPECIFIC_ANALYSIS, "analysis-uuid");
 
     underTest.delete(dbSession, "proj-uuid", "branch-uuid");
     db.commit();
@@ -280,12 +246,7 @@ public class NewCodePeriodDaoTest {
 
   @Test
   public void delete_by_project_uuid() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
-
-    underTest.insert(dbSession, new NewCodePeriodDto()
-      .setProjectUuid("proj-uuid")
-      .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
-      .setValue("analysis-uuid"));
+    insert("proj-uuid", null, SPECIFIC_ANALYSIS, "analysis-uuid");
 
     underTest.delete(dbSession, "proj-uuid", null);
     db.commit();
@@ -294,11 +255,7 @@ public class NewCodePeriodDaoTest {
 
   @Test
   public void delete_global() {
-    when(uuidFactory.create()).thenReturn(NEW_CODE_PERIOD_UUID);
-
-    underTest.insert(dbSession, new NewCodePeriodDto()
-      .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
-      .setValue("analysis-uuid"));
+    insert(null, null, SPECIFIC_ANALYSIS, "analysis-uuid");
 
     underTest.delete(dbSession, null, null);
     db.commit();
@@ -336,4 +293,12 @@ public class NewCodePeriodDaoTest {
     assertThat(db.countRowsOfTable("new_code_periods"))
       .isEqualTo(expected);
   }
+
+  private void insert(@Nullable String projectUuid, @Nullable String branchUuid, NewCodePeriodType type, @Nullable String value) {
+    underTest.insert(dbSession, new NewCodePeriodDto()
+      .setProjectUuid(projectUuid)
+      .setBranchUuid(branchUuid)
+      .setType(type)
+      .setValue(value));
+  }
 }
index 1c4356cc5d0587cbff76a3c0743ad5761e1e6da6..1b393d2d3c14dabb1a55379f178edc04636001f0 100644 (file)
@@ -36,6 +36,7 @@ import org.sonar.server.platform.db.migration.version.v85.DbVersion85;
 import org.sonar.server.platform.db.migration.version.v86.DbVersion86;
 import org.sonar.server.platform.db.migration.version.v87.DbVersion87;
 import org.sonar.server.platform.db.migration.version.v88.DbVersion88;
+import org.sonar.server.platform.db.migration.version.v89.DbVersion89;
 
 public class MigrationConfigurationModule extends Module {
   @Override
@@ -52,6 +53,7 @@ public class MigrationConfigurationModule extends Module {
       DbVersion86.class,
       DbVersion87.class,
       DbVersion88.class,
+      DbVersion89.class,
 
       // migration steps
       MigrationStepRegistryImpl.class,
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTable.java
new file mode 100644 (file)
index 0000000..4730a5a
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.platform.db.migration.version.v89;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddIndicesToNewCodePeriodTable extends DdlChange {
+  private static final String TABLE_NAME = "new_code_periods";
+  private static final String TYPE_INDEX_NAME = "idx_ncp_type";
+  private static final String VALUE_INDEX_NAME = "idx_ncp_value";
+
+  public AddIndicesToNewCodePeriodTable(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    if (!indexExists(TYPE_INDEX_NAME)) {
+      context.execute(new CreateIndexBuilder()
+        .setUnique(false)
+        .setTable(TABLE_NAME)
+        .setName(TYPE_INDEX_NAME)
+        .addColumn(newVarcharColumnDefBuilder()
+          .setColumnName("type")
+          .setIsNullable(false)
+          .setLimit(30)
+          .build())
+        .build());
+    }
+
+    if (!indexExists(VALUE_INDEX_NAME)) {
+      context.execute(new CreateIndexBuilder()
+        .setUnique(false)
+        .setTable(TABLE_NAME)
+        .setName(VALUE_INDEX_NAME)
+        .addColumn(newVarcharColumnDefBuilder()
+          .setColumnName("value")
+          .setIsNullable(true)
+          .setLimit(VarcharColumnDef.UUID_SIZE)
+          .build())
+        .build());
+    }
+  }
+
+  private boolean indexExists(String index) throws SQLException {
+    try (Connection connection = getDatabase().getDataSource().getConnection()) {
+      return DatabaseUtils.indexExistsIgnoreCase(TABLE_NAME, index, connection);
+    }
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v89/DbVersion89.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v89/DbVersion89.java
new file mode 100644 (file)
index 0000000..b160344
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.platform.db.migration.version.v89;
+
+import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
+import org.sonar.server.platform.db.migration.version.DbVersion;
+
+public class DbVersion89 implements DbVersion {
+
+  @Override
+  public void addSteps(MigrationStepRegistry registry) {
+    registry
+      .add(4400, "Add indices on columns 'type' and 'value' to 'new_code_periods' table", AddIndicesToNewCodePeriodTable.class);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTableTest.java
new file mode 100644 (file)
index 0000000..f584b63
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.platform.db.migration.version.v89;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+
+public class AddIndicesToNewCodePeriodTableTest {
+  private static final String TABLE_NAME = "new_code_periods";
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(AddIndicesToNewCodePeriodTableTest.class, "schema.sql");
+
+  private final MigrationStep underTest = new AddIndicesToNewCodePeriodTable(db.database());
+
+  @Test
+  public void execute() throws SQLException {
+    underTest.execute();
+
+    db.assertIndex(TABLE_NAME, "idx_ncp_type", "type");
+    db.assertIndex(TABLE_NAME, "idx_ncp_value", "value");
+  }
+
+  @Test
+  public void migration_is_re_entrant() throws SQLException {
+    underTest.execute();
+
+    // re-entrant
+    underTest.execute();
+
+    db.assertIndex(TABLE_NAME, "idx_ncp_type", "type");
+    db.assertIndex(TABLE_NAME, "idx_ncp_value", "value");
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v89/DbVersion89Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v89/DbVersion89Test.java
new file mode 100644 (file)
index 0000000..7ee1b1d
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.platform.db.migration.version.v89;
+
+import org.junit.Test;
+import org.sonar.server.platform.db.migration.version.DbVersion;
+
+import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationNotEmpty;
+import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber;
+
+public class DbVersion89Test {
+
+  private final DbVersion underTest = new DbVersion89();
+
+  @Test
+  public void migrationNumber_starts_at_4400() {
+    verifyMinimumMigrationNumber(underTest, 4400);
+  }
+
+  @Test
+  public void verify_migration_count() {
+    verifyMigrationNotEmpty(underTest);
+  }
+
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v89/AddIndicesToNewCodePeriodTableTest/schema.sql
new file mode 100644 (file)
index 0000000..534c0eb
--- /dev/null
@@ -0,0 +1,11 @@
+CREATE TABLE "NEW_CODE_PERIODS"(
+    "UUID" VARCHAR(40) NOT NULL,
+    "PROJECT_UUID" VARCHAR(40),
+    "BRANCH_UUID" VARCHAR(40),
+    "TYPE" VARCHAR(30) NOT NULL,
+    "VALUE" VARCHAR(40),
+    "UPDATED_AT" BIGINT NOT NULL,
+    "CREATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "NEW_CODE_PERIODS" ADD CONSTRAINT "PK_NEW_CODE_PERIODS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "UNIQ_NEW_CODE_PERIODS" ON "NEW_CODE_PERIODS"("PROJECT_UUID", "BRANCH_UUID");