Browse Source

SONAR-12636 add a global setting that defines default list of branches to keep

tags/8.1.0.31237
Michal Duda 4 years ago
parent
commit
d23f59d180
39 changed files with 1078 additions and 85 deletions
  1. 5
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java
  2. 19
    9
      server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDto.java
  3. 4
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java
  4. 6
    6
      server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeConfiguration.java
  5. 4
    2
      server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java
  6. 1
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java
  7. 15
    3
      server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml
  8. 2
    2
      server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
  9. 2
    1
      server/sonar-db-dao/src/schema/schema-sq.ddl
  10. 16
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java
  11. 2
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDtoTest.java
  12. 3
    3
      server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeConfigurationTest.java
  13. 27
    30
      server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java
  14. 1
    2
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/DbVersion80.java
  15. 49
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/AddExcludeBranchFromPurgeColumn.java
  16. 4
    1
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81.java
  17. 106
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateDefaultBranchesToKeepSetting.java
  18. 45
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/PopulateExcludeBranchFromPurgeColumn.java
  19. 94
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/AddExcludeBranchFromPurgeColumnTest.java
  20. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81Test.java
  21. 165
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateDefaultBranchesToKeepSettingTest.java
  22. 109
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/PopulateExcludeBranchFromPurgeColumnTest.java
  23. 13
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v80/MakeExcludeBranchFromPurgeColumnNotNullableTest/schema.sql
  24. 12
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/AddExcludeBranchFromPurgeColumnTest/schema.sql
  25. 11
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateDefaultBranchesToKeepSettingTest/schema.sql
  26. 13
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/PopulateExcludeBranchFromPurgeColumnTest/schema.sql
  27. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/branch/ws/BranchWsModule.java
  28. 2
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/branch/ws/BranchesWs.java
  29. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/branch/ws/ListAction.java
  30. 2
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/branch/ws/ProjectBranchesParameters.java
  31. 98
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/branch/ws/SetAutomaticDeletionProtectionAction.java
  32. 4
    2
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/branch/ws/list-example.json
  33. 1
    1
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/branch/ws/BranchWsModuleTest.java
  34. 208
    0
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/branch/ws/SetAutomaticDeletionProtectionActionTest.java
  35. 2
    1
      sonar-core/src/main/java/org/sonar/core/config/PurgeConstants.java
  36. 13
    15
      sonar-core/src/main/java/org/sonar/core/config/PurgeProperties.java
  37. 3
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties
  38. 13
    1
      sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
  39. 1
    0
      sonar-ws/src/main/protobuf/ws-projectbranches.proto

+ 5
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java View File

return mapper(dbSession).updateMainBranchName(projectUuid, newBranchKey, now); return mapper(dbSession).updateMainBranchName(projectUuid, newBranchKey, now);
} }


public int updateExcludeFromPurge(DbSession dbSession, String branchUuid, boolean excludeFromPurge) {
long now = system2.now();
return mapper(dbSession).updateExcludeFromPurge(branchUuid, excludeFromPurge, now);
}

public Optional<BranchDto> selectByBranchKey(DbSession dbSession, String projectUuid, String key) { public Optional<BranchDto> selectByBranchKey(DbSession dbSession, String projectUuid, String key) {
return selectByKey(dbSession, projectUuid, key, KeyType.BRANCH); return selectByKey(dbSession, projectUuid, key, KeyType.BRANCH);
} }

+ 19
- 9
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDto.java View File

@Nullable @Nullable
private byte[] pullRequestBinary; private byte[] pullRequestBinary;


private boolean excludeFromPurge;

public String getUuid() { public String getUuid() {
return uuid; return uuid;
} }
return decodePullRequestData(pullRequestBinary); return decodePullRequestData(pullRequestBinary);
} }


public boolean isExcludeFromPurge() {
return excludeFromPurge;
}

public void setExcludeFromPurge(boolean excludeFromPurge) {
this.excludeFromPurge = excludeFromPurge;
}

private static byte[] encodePullRequestData(DbProjectBranches.PullRequestData pullRequestData) { private static byte[] encodePullRequestData(DbProjectBranches.PullRequestData pullRequestData) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try { try {


@Override @Override
public String toString() { public String toString() {
return new StringBuilder("BranchDto{")
.append("uuid='").append(uuid).append('\'')
.append(", projectUuid='").append(projectUuid).append('\'')
.append(", kee='").append(kee).append('\'')
.append(", keyType=").append(keyType)
.append(", branchType=").append(branchType)
.append(", mergeBranchUuid='").append(mergeBranchUuid).append('\'')
.append('}')
.toString();
return "BranchDto{" +
"uuid='" + uuid + '\'' +
", projectUuid='" + projectUuid + '\'' +
", kee='" + kee + '\'' +
", keyType=" + keyType +
", branchType=" + branchType +
", mergeBranchUuid='" + mergeBranchUuid + '\'' +
", excludeFromPurge=" + excludeFromPurge +
'}';
} }
} }

+ 4
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java View File



int updateManualBaseline(@Param("uuid") String uuid, @Nullable @Param("analysisUuid") String analysisUuid, @Param("now") long now); int updateManualBaseline(@Param("uuid") String uuid, @Nullable @Param("analysisUuid") String analysisUuid, @Param("now") long now);


int updateExcludeFromPurge(@Param("uuid") String uuid, @Param("excludeFromPurge") boolean excludeFromPurge,
@Param("now") long now);

BranchDto selectByKey(@Param("projectUuid") String projectUuid, @Param("key") String key, @Param("keyType") KeyType keyType); BranchDto selectByKey(@Param("projectUuid") String projectUuid, @Param("key") String key, @Param("keyType") KeyType keyType);


BranchDto selectByUuid(@Param("uuid") String uuid); BranchDto selectByUuid(@Param("uuid") String uuid);


long countNonMainBranches(); long countNonMainBranches();


long countByTypeAndCreationDate(@Param("branchType")String branchType, @Param("sinceDate") long sinceDate);
long countByTypeAndCreationDate(@Param("branchType") String branchType, @Param("sinceDate") long sinceDate);
} }

+ 6
- 6
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeConfiguration.java View File

private final String projectUuid; private final String projectUuid;
private final Collection<String> scopesWithoutHistoricalData; private final Collection<String> scopesWithoutHistoricalData;
private final int maxAgeInDaysOfClosedIssues; private final int maxAgeInDaysOfClosedIssues;
private final Optional<Integer> maxAgeInDaysOfInactiveShortLivingBranches;
private final Optional<Integer> maxAgeInDaysOfInactiveBranches;
private final System2 system2; private final System2 system2;
private final Set<String> disabledComponentUuids; private final Set<String> disabledComponentUuids;


public PurgeConfiguration(String rootUuid, String projectUuid, Collection<String> scopesWithoutHistoricalData, int maxAgeInDaysOfClosedIssues, public PurgeConfiguration(String rootUuid, String projectUuid, Collection<String> scopesWithoutHistoricalData, int maxAgeInDaysOfClosedIssues,
Optional<Integer> maxAgeInDaysOfInactiveShortLivingBranches, System2 system2, Set<String> disabledComponentUuids) {
Optional<Integer> maxAgeInDaysOfInactiveBranches, System2 system2, Set<String> disabledComponentUuids) {
this.rootUuid = rootUuid; this.rootUuid = rootUuid;
this.projectUuid = projectUuid; this.projectUuid = projectUuid;
this.scopesWithoutHistoricalData = scopesWithoutHistoricalData; this.scopesWithoutHistoricalData = scopesWithoutHistoricalData;
this.maxAgeInDaysOfClosedIssues = maxAgeInDaysOfClosedIssues; this.maxAgeInDaysOfClosedIssues = maxAgeInDaysOfClosedIssues;
this.system2 = system2; this.system2 = system2;
this.disabledComponentUuids = disabledComponentUuids; this.disabledComponentUuids = disabledComponentUuids;
this.maxAgeInDaysOfInactiveShortLivingBranches = maxAgeInDaysOfInactiveShortLivingBranches;
this.maxAgeInDaysOfInactiveBranches = maxAgeInDaysOfInactiveBranches;
} }


public static PurgeConfiguration newDefaultPurgeConfiguration(Configuration config, String rootUuid, String projectUuid, Set<String> disabledComponentUuids) { public static PurgeConfiguration newDefaultPurgeConfiguration(Configuration config, String rootUuid, String projectUuid, Set<String> disabledComponentUuids) {
return new PurgeConfiguration(rootUuid, projectUuid, Arrays.asList(Scopes.DIRECTORY, Scopes.FILE), config.getInt(PurgeConstants.DAYS_BEFORE_DELETING_CLOSED_ISSUES).get(), return new PurgeConfiguration(rootUuid, projectUuid, Arrays.asList(Scopes.DIRECTORY, Scopes.FILE), config.getInt(PurgeConstants.DAYS_BEFORE_DELETING_CLOSED_ISSUES).get(),
config.getInt(PurgeConstants.DAYS_BEFORE_DELETING_INACTIVE_SHORT_LIVING_BRANCHES), System2.INSTANCE, disabledComponentUuids);
config.getInt(PurgeConstants.DAYS_BEFORE_DELETING_INACTIVE_BRANCHES), System2.INSTANCE, disabledComponentUuids);
} }


/** /**
return maxLiveDateOfClosedIssues(new Date(system2.now())); return maxLiveDateOfClosedIssues(new Date(system2.now()));
} }


public Optional<Date> maxLiveDateOfInactiveShortLivingBranches() {
return maxAgeInDaysOfInactiveShortLivingBranches.map(age -> DateUtils.addDays(new Date(system2.now()), -age));
public Optional<Date> maxLiveDateOfInactiveBranches() {
return maxAgeInDaysOfInactiveBranches.map(age -> DateUtils.addDays(new Date(system2.now()), -age));
} }


@VisibleForTesting @VisibleForTesting

+ 4
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java View File

import org.sonar.db.component.ComponentTreeQuery.Strategy; import org.sonar.db.component.ComponentTreeQuery.Strategy;


import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static org.sonar.api.utils.DateUtils.dateToLong; import static org.sonar.api.utils.DateUtils.dateToLong;
import static org.sonar.db.DatabaseUtils.executeLargeInputs; import static org.sonar.db.DatabaseUtils.executeLargeInputs;


} }


private static void purgeStaleBranches(PurgeCommands commands, PurgeConfiguration conf, PurgeMapper mapper, String rootUuid) { private static void purgeStaleBranches(PurgeCommands commands, PurgeConfiguration conf, PurgeMapper mapper, String rootUuid) {
Optional<Date> maxDate = conf.maxLiveDateOfInactiveShortLivingBranches();
Optional<Date> maxDate = conf.maxLiveDateOfInactiveBranches();
if (!maxDate.isPresent()) { if (!maxDate.isPresent()) {
// not available if branch plugin is not installed // not available if branch plugin is not installed
return; return;
} }
LOG.debug("<- Purge stale branches"); LOG.debug("<- Purge stale branches");


List<String> branchUuids = mapper.selectStaleShortLivingBranchesAndPullRequests(conf.projectUuid(), dateToLong(maxDate.get()));
Long maxDateValue = ofNullable(dateToLong(maxDate.get())).orElseThrow(IllegalStateException::new);
List<String> branchUuids = mapper.selectStaleBranchesAndPullRequests(conf.projectUuid(), maxDateValue);


for (String branchUuid : branchUuids) { for (String branchUuid : branchUuids) {
if (!rootUuid.equals(branchUuid)) { if (!rootUuid.equals(branchUuid)) {

+ 1
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java View File



List<String> selectOldClosedIssueKeys(@Param("projectUuid") String projectUuid, @Nullable @Param("toDate") Long toDate); List<String> selectOldClosedIssueKeys(@Param("projectUuid") String projectUuid, @Nullable @Param("toDate") Long toDate);


List<String> selectStaleShortLivingBranchesAndPullRequests(@Param("projectUuid") String projectUuid, @Param("toDate") Long toDate);
List<String> selectStaleBranchesAndPullRequests(@Param("projectUuid") String projectUuid, @Param("toDate") Long toDate);


@CheckForNull @CheckForNull
String selectSpecificAnalysisNewCodePeriod(@Param("projectUuid") String projectUuid); String selectSpecificAnalysisNewCodePeriod(@Param("projectUuid") String projectUuid);

+ 15
- 3
server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml View File

pb.key_type as keyType, pb.key_type as keyType,
pb.branch_type as branchType, pb.branch_type as branchType,
pb.merge_branch_uuid as mergeBranchUuid, pb.merge_branch_uuid as mergeBranchUuid,
pb.pull_request_binary as pullRequestBinary
pb.pull_request_binary as pullRequestBinary,
pb.exclude_from_purge as excludeFromPurge
</sql> </sql>


<insert id="insert" parameterType="map" useGeneratedKeys="false"> <insert id="insert" parameterType="map" useGeneratedKeys="false">
merge_branch_uuid, merge_branch_uuid,
pull_request_binary, pull_request_binary,
created_at, created_at,
updated_at
updated_at,
exclude_from_purge
) values ( ) values (
#{dto.uuid, jdbcType=VARCHAR}, #{dto.uuid, jdbcType=VARCHAR},
#{dto.projectUuid, jdbcType=VARCHAR}, #{dto.projectUuid, jdbcType=VARCHAR},
#{dto.mergeBranchUuid, jdbcType=VARCHAR}, #{dto.mergeBranchUuid, jdbcType=VARCHAR},
#{dto.pullRequestBinary, jdbcType=BINARY}, #{dto.pullRequestBinary, jdbcType=BINARY},
#{now, jdbcType=BIGINT}, #{now, jdbcType=BIGINT},
#{now, jdbcType=BIGINT}
#{now, jdbcType=BIGINT},
#{dto.excludeFromPurge, jdbcType=BOOLEAN}
) )
</insert> </insert>


uuid = #{projectUuid, jdbcType=VARCHAR} uuid = #{projectUuid, jdbcType=VARCHAR}
</update> </update>


<update id="updateExcludeFromPurge">
update project_branches
set
exclude_from_purge = #{excludeFromPurge, jdbcType=BOOLEAN},
updated_at = #{now, jdbcType=BIGINT}
where
uuid = #{uuid, jdbcType=VARCHAR}
</update>

<update id="update" parameterType="map" useGeneratedKeys="false"> <update id="update" parameterType="map" useGeneratedKeys="false">
update project_branches update project_branches
set set

+ 2
- 2
server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml View File

AND ncp.branch_uuid=#{projectUuid,jdbcType=VARCHAR} AND ncp.branch_uuid=#{projectUuid,jdbcType=VARCHAR}
</select> </select>


<select id="selectStaleShortLivingBranchesAndPullRequests" parameterType="map" resultType="String">
<select id="selectStaleBranchesAndPullRequests" parameterType="map" resultType="String">
select select
pb.uuid pb.uuid
from from
project_branches pb project_branches pb
where where
pb.project_uuid=#{projectUuid,jdbcType=VARCHAR} pb.project_uuid=#{projectUuid,jdbcType=VARCHAR}
and (pb.branch_type='SHORT' or pb.branch_type='PULL_REQUEST')
and pb.exclude_from_purge = ${_false}
and pb.updated_at &lt; #{toDate} and pb.updated_at &lt; #{toDate}
</select> </select>



+ 2
- 1
server/sonar-db-dao/src/schema/schema-sq.ddl View File

"PULL_REQUEST_BINARY" BLOB, "PULL_REQUEST_BINARY" BLOB,
"MANUAL_BASELINE_ANALYSIS_UUID" VARCHAR(40), "MANUAL_BASELINE_ANALYSIS_UUID" VARCHAR(40),
"CREATED_AT" BIGINT NOT NULL, "CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL
"UPDATED_AT" BIGINT NOT NULL,
"EXCLUDE_FROM_PURGE" BOOLEAN DEFAULT FALSE NOT NULL
); );
ALTER TABLE "PROJECT_BRANCHES" ADD CONSTRAINT "PK_PROJECT_BRANCHES" PRIMARY KEY("UUID"); ALTER TABLE "PROJECT_BRANCHES" ADD CONSTRAINT "PK_PROJECT_BRANCHES" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE_KEY_TYPE" ON "PROJECT_BRANCHES"("PROJECT_UUID", "KEE", "KEY_TYPE"); CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE_KEY_TYPE" ON "PROJECT_BRANCHES"("PROJECT_UUID", "KEE", "KEY_TYPE");

+ 16
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java View File

assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG); assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG);
} }


@Test
public void updateExcludeFromPurge() {
BranchDto dto = new BranchDto();
dto.setProjectUuid("U1");
dto.setUuid("U1");
dto.setBranchType(BranchType.LONG);
dto.setKey("feature");
dto.setExcludeFromPurge(false);
underTest.insert(dbSession, dto);

underTest.updateExcludeFromPurge(dbSession, "U1", true);

BranchDto loaded = underTest.selectByBranchKey(dbSession, "U1", "feature").get();
assertThat(loaded.isExcludeFromPurge()).isTrue();
}

@DataProvider @DataProvider
public static Object[][] nullOrEmpty() { public static Object[][] nullOrEmpty() {
return new Object[][] { return new Object[][] {

+ 2
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDtoTest.java View File

underTest.setKey("K1"); underTest.setKey("K1");
underTest.setBranchType(BranchType.LONG); underTest.setBranchType(BranchType.LONG);
underTest.setMergeBranchUuid("U3"); underTest.setMergeBranchUuid("U3");
underTest.setExcludeFromPurge(true);


assertThat(underTest.toString()).isEqualTo("BranchDto{uuid='U1', " + assertThat(underTest.toString()).isEqualTo("BranchDto{uuid='U1', " +
"projectUuid='U2', kee='K1', keyType=null, branchType=LONG, mergeBranchUuid='U3'}");
"projectUuid='U2', kee='K1', keyType=null, branchType=LONG, mergeBranchUuid='U3', excludeFromPurge=true}");
} }


@Test @Test

+ 3
- 3
server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeConfigurationTest.java View File

@Test @Test
public void should_have_empty_branch_purge_date() { public void should_have_empty_branch_purge_date() {
PurgeConfiguration conf = new PurgeConfiguration("root", "project", emptySet(), 30, Optional.of(10), System2.INSTANCE, emptySet()); PurgeConfiguration conf = new PurgeConfiguration("root", "project", emptySet(), 30, Optional.of(10), System2.INSTANCE, emptySet());
assertThat(conf.maxLiveDateOfInactiveShortLivingBranches()).isNotEmpty();
assertThat(conf.maxLiveDateOfInactiveBranches()).isNotEmpty();
long tenDaysAgo = DateUtils.addDays(new Date(System2.INSTANCE.now()), -10).getTime(); long tenDaysAgo = DateUtils.addDays(new Date(System2.INSTANCE.now()), -10).getTime();
assertThat(conf.maxLiveDateOfInactiveShortLivingBranches().get().getTime()).isBetween(tenDaysAgo - 5000, tenDaysAgo + 5000);
assertThat(conf.maxLiveDateOfInactiveBranches().get().getTime()).isBetween(tenDaysAgo - 5000, tenDaysAgo + 5000);
} }


@Test @Test
public void should_calculate_branch_purge_date() { public void should_calculate_branch_purge_date() {
PurgeConfiguration conf = new PurgeConfiguration("root", "project", emptySet(), 30, Optional.empty(), System2.INSTANCE, emptySet()); PurgeConfiguration conf = new PurgeConfiguration("root", "project", emptySet(), 30, Optional.empty(), System2.INSTANCE, emptySet());
assertThat(conf.maxLiveDateOfInactiveShortLivingBranches()).isEmpty();
assertThat(conf.maxLiveDateOfInactiveBranches()).isEmpty();
} }


@Test @Test

+ 27
- 30
server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java View File

public void purge_failed_ce_tasks() { public void purge_failed_ce_tasks() {
ComponentDto project = db.components().insertPrivateProject(); ComponentDto project = db.components().insertPrivateProject();
SnapshotDto pastAnalysis = db.components().insertSnapshot(project, t -> t.setStatus(STATUS_PROCESSED).setLast(false)); SnapshotDto pastAnalysis = db.components().insertSnapshot(project, t -> t.setStatus(STATUS_PROCESSED).setLast(false));
SnapshotDto toBeDeletedAnalysis = db.components().insertSnapshot(project, t -> t.setStatus(STATUS_UNPROCESSED).setLast(false));
db.components().insertSnapshot(project, t -> t.setStatus(STATUS_UNPROCESSED).setLast(false));
SnapshotDto lastAnalysis = db.components().insertSnapshot(project, t -> t.setStatus(STATUS_PROCESSED).setLast(true)); SnapshotDto lastAnalysis = db.components().insertSnapshot(project, t -> t.setStatus(STATUS_PROCESSED).setLast(true));


underTest.purge(dbSession, newConfigurationWith30Days(project.uuid()), PurgeListener.EMPTY, new PurgeProfiler()); underTest.purge(dbSession, newConfigurationWith30Days(project.uuid()), PurgeListener.EMPTY, new PurgeProfiler());
} }


@Test @Test
public void purge_inactive_short_living_branches() {
public void purge_inactive_branches() {
when(system2.now()).thenReturn(new Date().getTime()); when(system2.now()).thenReturn(new Date().getTime());
RuleDefinitionDto rule = db.rules().insert(); RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertMainBranch(); ComponentDto project = db.components().insertMainBranch();
ComponentDto longBranch = db.components().insertProjectBranch(project);
ComponentDto recentShortBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.SHORT));
ComponentDto branch1 = db.components().insertProjectBranch(project);
ComponentDto branch2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH));


// short branch with other components and issues, updated 31 days ago
// branch with other components and issues, updated 31 days ago
when(system2.now()).thenReturn(DateUtils.addDays(new Date(), -31).getTime()); when(system2.now()).thenReturn(DateUtils.addDays(new Date(), -31).getTime());
ComponentDto shortBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.SHORT));
ComponentDto module = db.components().insertComponent(newModuleDto(shortBranch));
ComponentDto branch3 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH));
ComponentDto module = db.components().insertComponent(newModuleDto(branch3));
ComponentDto subModule = db.components().insertComponent(newModuleDto(module)); ComponentDto subModule = db.components().insertComponent(newModuleDto(module));
ComponentDto file = db.components().insertComponent(newFileDto(subModule)); ComponentDto file = db.components().insertComponent(newFileDto(subModule));
db.issues().insert(rule, shortBranch, file);
db.issues().insert(rule, shortBranch, subModule);
db.issues().insert(rule, shortBranch, module);
db.issues().insert(rule, branch3, file);
db.issues().insert(rule, branch3, subModule);
db.issues().insert(rule, branch3, module);


underTest.purge(dbSession, newConfigurationWith30Days(System2.INSTANCE, project.uuid(), project.uuid()), PurgeListener.EMPTY, new PurgeProfiler()); underTest.purge(dbSession, newConfigurationWith30Days(System2.INSTANCE, project.uuid(), project.uuid()), PurgeListener.EMPTY, new PurgeProfiler());
dbSession.commit(); dbSession.commit();


assertThat(uuidsIn("projects")).containsOnly(project.uuid(), longBranch.uuid(), recentShortBranch.uuid());
assertThat(uuidsIn("projects")).containsOnly(project.uuid(), branch1.uuid(), branch2.uuid());
} }


@Test @Test
} }


@Test @Test
public void purge_inactive_SLB_when_analyzing_non_main_branch() {
public void purge_inactive_branches_when_analyzing_non_main_branch() {
when(system2.now()).thenReturn(new Date().getTime()); when(system2.now()).thenReturn(new Date().getTime());
RuleDefinitionDto rule = db.rules().insert(); RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertMainBranch(); ComponentDto project = db.components().insertMainBranch();


when(system2.now()).thenReturn(DateUtils.addDays(new Date(), -31).getTime()); when(system2.now()).thenReturn(DateUtils.addDays(new Date(), -31).getTime());


// SLB updated 31 days ago
ComponentDto slb1 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.SHORT));
// branch updated 31 days ago
ComponentDto branch1 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH));


// SLB with other components and issues, updated 31 days ago
ComponentDto slb2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST));
ComponentDto file = db.components().insertComponent(newFileDto(slb2));
db.issues().insert(rule, slb2, file);
// branch with other components and issues, updated 31 days ago
ComponentDto branch2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST));
ComponentDto file = db.components().insertComponent(newFileDto(branch2));
db.issues().insert(rule, branch2, file);


// back to present // back to present
when(system2.now()).thenReturn(new Date().getTime()); when(system2.now()).thenReturn(new Date().getTime());
// analysing slb1
underTest.purge(dbSession, newConfigurationWith30Days(system2, slb1.uuid(), slb1.getMainBranchProjectUuid()), PurgeListener.EMPTY, new PurgeProfiler());
// analysing branch1
underTest.purge(dbSession, newConfigurationWith30Days(system2, branch1.uuid(), branch1.getMainBranchProjectUuid()), PurgeListener.EMPTY, new PurgeProfiler());
dbSession.commit(); dbSession.commit();


// slb1 wasn't deleted since it was being analyzed!
assertThat(uuidsIn("projects")).containsOnly(project.uuid(), longBranch.uuid(), slb1.uuid());
// branch1 wasn't deleted since it was being analyzed!
assertThat(uuidsIn("projects")).containsOnly(project.uuid(), longBranch.uuid(), branch1.uuid());
} }


@Test @Test
.setProjectUuid(project1.uuid()) .setProjectUuid(project1.uuid())
.setBranchUuid(project1.uuid()) .setBranchUuid(project1.uuid())
.setType(NewCodePeriodType.SPECIFIC_ANALYSIS) .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
.setValue(analysis1.getUuid())
);
.setValue(analysis1.getUuid()));
ComponentDto project2 = db.components().insertPrivateProject(); ComponentDto project2 = db.components().insertPrivateProject();
SnapshotDto analysis2 = db.components().insertSnapshot(newSnapshot() SnapshotDto analysis2 = db.components().insertSnapshot(newSnapshot()
.setComponentUuid(project2.uuid()) .setComponentUuid(project2.uuid())
.setProjectUuid(project.uuid()) .setProjectUuid(project.uuid())
.setBranchUuid(project.uuid()) .setBranchUuid(project.uuid())
.setType(NewCodePeriodType.SPECIFIC_ANALYSIS) .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
.setValue(analysisProject.getUuid())
);
.setValue(analysisProject.getUuid()));
ComponentDto branch1 = db.components().insertProjectBranch(project); ComponentDto branch1 = db.components().insertProjectBranch(project);
SnapshotDto analysisBranch1 = db.components().insertSnapshot(newSnapshot() SnapshotDto analysisBranch1 = db.components().insertSnapshot(newSnapshot()
.setComponentUuid(branch1.uuid()) .setComponentUuid(branch1.uuid())
.setProjectUuid(project.uuid()) .setProjectUuid(project.uuid())
.setBranchUuid(branch2.uuid()) .setBranchUuid(branch2.uuid())
.setType(NewCodePeriodType.SPECIFIC_ANALYSIS) .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
.setValue(analysisBranch2.getUuid())
);
.setValue(analysisBranch2.getUuid()));
dbSession.commit(); dbSession.commit();


assertThat(underTest.selectPurgeableAnalyses(project.uuid(), dbSession)) assertThat(underTest.selectPurgeableAnalyses(project.uuid(), dbSession))
@Test @Test
public void delete_ce_analysis_older_than_180_and_scanner_context_older_than_40_days_of_project_and_branches_when_purging_project() { public void delete_ce_analysis_older_than_180_and_scanner_context_older_than_40_days_of_project_and_branches_when_purging_project() {
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
ComponentDto project1 = db.components().insertPublicProject();
ComponentDto branch1 = db.components().insertProjectBranch(project1);
ComponentDto project1 = db.components().insertMainBranch();
ComponentDto branch1 = db.components().insertProjectBranch(project1, b -> b.setExcludeFromPurge(true));
Consumer<CeQueueDto> belongsToProject1 = t -> t.setMainComponentUuid(project1.uuid()).setComponentUuid(project1.uuid()); Consumer<CeQueueDto> belongsToProject1 = t -> t.setMainComponentUuid(project1.uuid()).setComponentUuid(project1.uuid());
Consumer<CeQueueDto> belongsToBranch1 = t -> t.setMainComponentUuid(project1.uuid()).setComponentUuid(branch1.uuid()); Consumer<CeQueueDto> belongsToBranch1 = t -> t.setMainComponentUuid(project1.uuid()).setComponentUuid(branch1.uuid());



+ 1
- 2
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/DbVersion80.java View File

.add(3006, "Create NEW_CODE_PERIOD table", CreateNewCodePeriodTable.class) .add(3006, "Create NEW_CODE_PERIOD table", CreateNewCodePeriodTable.class)
.add(3007, "Populate NEW_CODE_PERIOD table", PopulateNewCodePeriodTable.class) .add(3007, "Populate NEW_CODE_PERIOD table", PopulateNewCodePeriodTable.class)
.add(3008, "Remove leak period properties", RemoveLeakPeriodProperties.class) .add(3008, "Remove leak period properties", RemoveLeakPeriodProperties.class)
.add(3009, "Remove GitHub login generation strategy property", RemoveGitHubLoginGenerationStrategyProperty.class)
;
.add(3009, "Remove GitHub login generation strategy property", RemoveGitHubLoginGenerationStrategyProperty.class);
} }
} }

+ 49
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/AddExcludeBranchFromPurgeColumn.java View File

/*
* SonarQube
* Copyright (C) 2009-2019 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.v81;

import java.sql.SQLException;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;
import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
import org.sonar.server.platform.db.migration.step.DdlChange;

import static org.sonar.server.platform.db.migration.def.BooleanColumnDef.newBooleanColumnDefBuilder;

@SupportsBlueGreen
public class AddExcludeBranchFromPurgeColumn extends DdlChange {
private static final String TABLE = "project_branches";
private static final String NEW_COLUMN = "exclude_from_purge";

public AddExcludeBranchFromPurgeColumn(Database db) {
super(db);
}

@Override
public void execute(Context context) throws SQLException {
context.execute(new AddColumnsBuilder(getDialect(), TABLE)
.addColumn(newBooleanColumnDefBuilder()
.setColumnName(NEW_COLUMN)
.setIsNullable(false)
.setDefaultValue(false)
.build())
.build());
}
}

+ 4
- 1
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81.java View File

.add(3103, "Migrate GitHub ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateGithubAlmSettings.class) .add(3103, "Migrate GitHub ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateGithubAlmSettings.class)
.add(3104, "Migrate Bitbucket ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateBitbucketAlmSettings.class) .add(3104, "Migrate Bitbucket ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateBitbucketAlmSettings.class)
.add(3105, "Migrate Azure ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateAzureAlmSettings.class) .add(3105, "Migrate Azure ALM settings from PROPERTIES to ALM_SETTINGS tables", MigrateAzureAlmSettings.class)
.add(3106, "Delete 'sonar.pullrequest.provider' property", DeleteSonarPullRequestProviderProperty.class);
.add(3106, "Delete 'sonar.pullrequest.provider' property", DeleteSonarPullRequestProviderProperty.class)
.add(3107, "Migrate default branches to keep global setting", MigrateDefaultBranchesToKeepSetting.class)
.add(3108, "Add EXCLUDE_FROM_PURGE column", AddExcludeBranchFromPurgeColumn.class)
.add(3109, "Populate EXCLUDE_FROM_PURGE column", PopulateExcludeBranchFromPurgeColumn.class);
} }
} }

+ 106
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateDefaultBranchesToKeepSetting.java View File

/*
* SonarQube
* Copyright (C) 2009-2019 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.v81;

import java.sql.SQLException;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;
import org.sonar.server.platform.db.migration.step.DataChange;
import org.sonar.server.platform.db.migration.step.MassUpdate;

import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;

@SupportsBlueGreen
public class MigrateDefaultBranchesToKeepSetting extends DataChange {
private static final Logger LOG = Loggers.get(MigrateDefaultBranchesToKeepSetting.class);
private static final String DEPRECATED_KEY = "sonar.branch.longLivedBranches.regex";
private static final String NEW_KEY = "sonar.dbcleaner.branchesToKeepWhenInactive";

private final System2 system;

public MigrateDefaultBranchesToKeepSetting(Database db, System2 system) {
super(db);
this.system = system;
}

@Override
protected void execute(Context context) throws SQLException {
Long now = system.now();
try {
Long numberOfNewProps = context.prepareSelect("select count(*) from properties props where props.prop_key = '" + NEW_KEY + "'")
.get(row -> row.getLong(1));
if (numberOfNewProps != null && numberOfNewProps > 0) {
// no need for a migration
return;
}

Boolean defaultPropertyOverridden = context.prepareSelect("select count(*) from properties props where props.prop_key = '" + DEPRECATED_KEY + "'")
.get(row -> row.getLong(1) > 0);
if (FALSE.equals(defaultPropertyOverridden)) {
migrateDefaultDeprecatedSettings(context, now);
} else {
migrateOverriddenDeprecatedSettings(context, now);
}
} catch (Exception ex) {
LOG.error("Failed to migrate to new '{}' setting.", NEW_KEY);
throw ex;
}
}

private static void migrateDefaultDeprecatedSettings(Context context, Long time) throws SQLException {
Boolean anyProjectAlreadyExists = context.prepareSelect("select count(*) from projects").get(row -> row.getLong(1) > 0);
String newSettingValue = "master,develop,trunk";

// if old `sonar.branch.longLivedBranches.regex` setting was at default value but there were already projects analyzed we need to add the
// old defaults of
// that setting to the defaults of the new `sonar.dbcleaner.branchesToKeepWhenInactive` setting for backward compatibility
if (TRUE.equals(anyProjectAlreadyExists)) {
newSettingValue = "master,develop,trunk,branch-.*,release-.*";
}

context
.prepareUpsert("insert into properties (prop_key, is_empty, text_value, created_at) values (?, ?, ?, ?)")
.setString(1, NEW_KEY)
.setBoolean(2, false)
.setString(3, newSettingValue)
.setLong(4, time)
.execute()
.commit();
}

private static void migrateOverriddenDeprecatedSettings(Context context, Long time) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select("select resource_id, text_value from properties props where props.prop_key = '" + DEPRECATED_KEY + "'");
massUpdate.rowPluralName("properties");
massUpdate.update("insert into properties (resource_id, prop_key, is_empty, text_value, created_at) values (?, ?, ?, ?, ?)");
massUpdate.execute((row, update) -> {
update.setLong(1, row.getNullableLong(1));
update.setString(2, NEW_KEY);
update.setBoolean(3, false);
update.setString(4, row.getString(2));
update.setLong(5, time);
return true;
});
}
}

+ 45
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/PopulateExcludeBranchFromPurgeColumn.java View File

/*
* SonarQube
* Copyright (C) 2009-2019 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.v81;

import java.sql.SQLException;
import org.sonar.api.utils.System2;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;
import org.sonar.server.platform.db.migration.step.DataChange;

@SupportsBlueGreen
public class PopulateExcludeBranchFromPurgeColumn extends DataChange {
private final System2 system;

public PopulateExcludeBranchFromPurgeColumn(Database db, System2 system) {
super(db);
this.system = system;
}

@Override
public void execute(Context context) throws SQLException {
Long now = system.now();
context.prepareUpsert("update project_branches set exclude_from_purge = true, updated_at = ? where branch_type = 'LONG'")
.setLong(1, now)
.execute()
.commit();
}
}

+ 94
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/AddExcludeBranchFromPurgeColumnTest.java View File

/*
* SonarQube
* Copyright (C) 2009-2019 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.v81;

import java.sql.SQLException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.db.CoreDbTester;

import static org.assertj.core.api.Assertions.assertThat;

public class AddExcludeBranchFromPurgeColumnTest {

private static final String TABLE = "project_branches";

private static final String PROJECT_1_UUID = "1a724f54-c2a4-4d36-b59c-61026f178613";
private static final String PROJECT_2_UUID = "cc69ccb8-434a-489b-abd6-6a80371f64ff";

private static final String MAIN_BRANCH_1 = "8a9789c8-7aee-4aa6-9cb7-407137935ac3";
private static final String LONG_BRANCH_1 = "69fdfc19-491c-4ed9-bcac-ef04e0f6cdc6";
private static final String SHORT_BRANCH_1 = "f7a6d247-1790-40f8-8591-a0cc39d05ecb";
private static final String PR_1 = "d65466bf-271c-4f9f-ac7a-72add44848c0";

private static final String MAIN_BRANCH_2 = "cdadf128-7bdb-4589-982d-62445d46ae1b";
private static final String LONG_BRANCH_2 = "60bf6fa8-3ecc-4ba6-ad8d-991ea7c7f9cb";
private static final String SHORT_BRANCH_2 = "ce5632ed-462e-4384-98b6-1773a7bbfe53";
private static final String PR_2 = "5897f7d0-d34f-4558-b9c5-a7eb7a5c4b69";

@Rule
public CoreDbTester dbTester = CoreDbTester.createForSchema(AddExcludeBranchFromPurgeColumnTest.class, "schema.sql");

@Rule
public ExpectedException expectedException = ExpectedException.none();

private AddExcludeBranchFromPurgeColumn underTest = new AddExcludeBranchFromPurgeColumn(dbTester.database());

@Before
public void setup() {
insertBranch(MAIN_BRANCH_1, PROJECT_1_UUID, "master", "BRANCH", "LONG");
insertBranch(LONG_BRANCH_1, PROJECT_1_UUID, "release-1", "BRANCH", "LONG");
insertBranch(SHORT_BRANCH_1, PROJECT_1_UUID, "feature/foo", "BRANCH", "SHORT");
insertBranch(PR_1, PROJECT_1_UUID, "feature/feature-1", "PULL_REQUEST", "PULL_REQUEST");

insertBranch(MAIN_BRANCH_2, PROJECT_2_UUID, "trunk", "BRANCH", "LONG");
insertBranch(LONG_BRANCH_2, PROJECT_2_UUID, "branch-1", "BRANCH", "LONG");
insertBranch(SHORT_BRANCH_2, PROJECT_2_UUID, "feature/bar", "BRANCH", "SHORT");
insertBranch(PR_2, PROJECT_2_UUID, "feature/feature-2", "PULL_REQUEST", "PULL_REQUEST");
}

@Test
public void execute_migration() throws SQLException {
underTest.execute();

verifyMigrationResult();
}

private void verifyMigrationResult() {
assertThat(dbTester.countSql("select count(*) from " + TABLE + " where exclude_from_purge = true")).isEqualTo(0);
assertThat(dbTester.countSql("select count(*) from " + TABLE + " where exclude_from_purge = false")).isEqualTo(8);
}

private void insertBranch(String uuid, String projectUuid, String key, String keyType, String branchType) {
dbTester.executeInsert(
TABLE,
"UUID", uuid,
"PROJECT_UUID", projectUuid,
"KEE", key,
"BRANCH_TYPE", branchType,
"KEY_TYPE", keyType,
"MERGE_BRANCH_UUID", null,
"CREATED_AT", System2.INSTANCE.now(),
"UPDATED_AT", System2.INSTANCE.now());
}
}

+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81Test.java View File



@Test @Test
public void verify_migration_count() { public void verify_migration_count() {
verifyMigrationCount(underTest, 7);
verifyMigrationCount(underTest, 10);
} }


} }

+ 165
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateDefaultBranchesToKeepSettingTest.java View File

/*
* SonarQube
* Copyright (C) 2009-2019 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.v81;

import java.sql.SQLException;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.assertj.core.groups.Tuple;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.core.util.Uuids;
import org.sonar.db.CoreDbTester;

import static org.assertj.core.api.Assertions.assertThat;

public class MigrateDefaultBranchesToKeepSettingTest {

private static final String PROPS_TABLE = "properties";
private static final String PATTERN_1 = "(branch|release|llb)-.*";
private static final String PATTERN_2 = "(branch|llb)-.*";
private static final String PATTERN_3 = "llb-.*";

@Rule
public CoreDbTester dbTester = CoreDbTester.createForSchema(MigrateDefaultBranchesToKeepSettingTest.class, "schema.sql");

@Rule
public ExpectedException expectedException = ExpectedException.none();

private MigrateDefaultBranchesToKeepSetting underTest = new MigrateDefaultBranchesToKeepSetting(dbTester.database(), System2.INSTANCE);

@Test
public void migrate_overridden_old_setting() throws SQLException {
setupOverriddenSetting();

underTest.execute();

verifyMigrationOfOverriddenSetting();
}

@Test
public void migration_of_overridden_setting_is_re_entrant() throws SQLException {
setupOverriddenSetting();

underTest.execute();
underTest.execute();

verifyMigrationOfOverriddenSetting();
}

@Test
public void migrate_default_old_setting_on_fresh_install() throws SQLException {
setupDefaultSetting();

underTest.execute();

verifyMigrationOfDefaultSetting("master,develop,trunk");
}

@Test
public void migrate_default_old_setting_on_existing_install() throws SQLException {
setupDefaultSetting();
insertProject();

underTest.execute();

verifyMigrationOfDefaultSetting("master,develop,trunk,branch-.*,release-.*");
}

@Test
public void migration_of_default_old_setting_is_re_entrant() throws SQLException {
setupDefaultSetting();

underTest.execute();
underTest.execute();

verifyMigrationOfDefaultSetting("master,develop,trunk");
}

private void setupOverriddenSetting() {
insertProperty(1, "some.key", "some.value", 1001L);
insertProperty(2, "sonar.branch.longLivedBranches.regex", PATTERN_1, 1001L);
insertProperty(3, "some.other.key", "some.other.value", 1001L);
insertProperty(4, "sonar.branch.longLivedBranches.regex", PATTERN_2, 1002L);
insertProperty(5, "some.other.key", "some.other.value", null);
insertProperty(6, "sonar.branch.longLivedBranches.regex", PATTERN_3, null);
}

private void setupDefaultSetting() {
insertProperty(1, "some.key", "some.value", 1001L);
insertProperty(3, "some.other.key", "some.other.value", 1001L);
insertProperty(5, "some.other.key", "some.other.value", null);
}

private void verifyMigrationOfOverriddenSetting() {
assertThat(dbTester.countRowsOfTable(PROPS_TABLE)).isEqualTo(9);
assertThat(dbTester.countSql("select count(*) from " + PROPS_TABLE + " where prop_key = 'sonar.branch.longLivedBranches.regex'")).isEqualTo(3);
assertThat(dbTester.countSql("select count(*) from " + PROPS_TABLE + " where prop_key = 'sonar.dbcleaner.branchesToKeepWhenInactive'")).isEqualTo(3);
assertThat(dbTester.select("select resource_id, text_value from " + PROPS_TABLE + " where prop_key = 'sonar.dbcleaner.branchesToKeepWhenInactive'")
.stream()
.map(e -> new Tuple(e.get("TEXT_VALUE"), e.get("RESOURCE_ID")))
.collect(Collectors.toList()))
.containsExactlyInAnyOrder(
new Tuple(PATTERN_1, 1001L),
new Tuple(PATTERN_2, 1002L),
new Tuple(PATTERN_3, null));
}

private void verifyMigrationOfDefaultSetting(String expectedValue) {
assertThat(dbTester.countRowsOfTable(PROPS_TABLE)).isEqualTo(4);
assertThat(dbTester.countSql("select count(*) from " + PROPS_TABLE + " where prop_key = 'sonar.branch.longLivedBranches.regex'")).isEqualTo(0);
assertThat(dbTester.countSql("select count(*) from " + PROPS_TABLE + " where prop_key = 'sonar.dbcleaner.branchesToKeepWhenInactive'")).isEqualTo(1);
assertThat(dbTester.select("select resource_id, text_value from " + PROPS_TABLE + " where prop_key = 'sonar.dbcleaner.branchesToKeepWhenInactive'")
.stream()
.map(e -> new Tuple(e.get("TEXT_VALUE"), e.get("RESOURCE_ID")))
.collect(Collectors.toList()))
.containsExactly(new Tuple(expectedValue, null));
}

private void insertProperty(int id, String key, String value, @Nullable Long resourceId) {
dbTester.executeInsert(
PROPS_TABLE,
"ID", id,
"PROP_KEY", key,
"RESOURCE_ID", resourceId,
"USER_ID", null,
"IS_EMPTY", false,
"TEXT_VALUE", value,
"CLOB_VALUE", null,
"CREATED_AT", System2.INSTANCE.now());
}

private void insertProject() {
String uuid = Uuids.createFast();
dbTester.executeInsert("PROJECTS",
"ORGANIZATION_UUID", "default-org",
"KEE", uuid + "-key",
"UUID", uuid,
"PROJECT_UUID", uuid,
"main_branch_project_uuid", uuid,
"UUID_PATH", ".",
"ROOT_UUID", uuid,
"PRIVATE", "true",
"qualifier", "TRK",
"scope", "PRJ");
}
}

+ 109
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/PopulateExcludeBranchFromPurgeColumnTest.java View File

/*
* SonarQube
* Copyright (C) 2009-2019 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.v81;

import java.sql.SQLException;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.db.CoreDbTester;

import static org.assertj.core.api.Assertions.assertThat;

public class PopulateExcludeBranchFromPurgeColumnTest {

private static final String TABLE = "PROJECT_BRANCHES";

private static final String PROJECT_1_UUID = "1a724f54-c2a4-4d36-b59c-61026f178613";
private static final String PROJECT_2_UUID = "cc69ccb8-434a-489b-abd6-6a80371f64ff";

private static final String MAIN_BRANCH_1 = "8a9789c8-7aee-4aa6-9cb7-407137935ac3";
private static final String LONG_BRANCH_1 = "69fdfc19-491c-4ed9-bcac-ef04e0f6cdc6";
private static final String SHORT_BRANCH_1 = "f7a6d247-1790-40f8-8591-a0cc39d05ecb";
private static final String PR_1 = "d65466bf-271c-4f9f-ac7a-72add44848c0";

private static final String MAIN_BRANCH_2 = "cdadf128-7bdb-4589-982d-62445d46ae1b";
private static final String LONG_BRANCH_2 = "60bf6fa8-3ecc-4ba6-ad8d-991ea7c7f9cb";
private static final String SHORT_BRANCH_2 = "ce5632ed-462e-4384-98b6-1773a7bbfe53";
private static final String PR_2 = "5897f7d0-d34f-4558-b9c5-a7eb7a5c4b69";

@Rule
public CoreDbTester dbTester = CoreDbTester.createForSchema(PopulateExcludeBranchFromPurgeColumnTest.class, "schema.sql");

@Rule
public ExpectedException expectedException = ExpectedException.none();

private PopulateExcludeBranchFromPurgeColumn underTest = new PopulateExcludeBranchFromPurgeColumn(dbTester.database(), System2.INSTANCE);

@Before
public void setup() {
insertBranch(MAIN_BRANCH_1, PROJECT_1_UUID, "master", "BRANCH", "LONG");
insertBranch(LONG_BRANCH_1, PROJECT_1_UUID, "release-1", "BRANCH", "LONG");
insertBranch(SHORT_BRANCH_1, PROJECT_1_UUID, "feature/foo", "BRANCH", "SHORT");
insertBranch(PR_1, PROJECT_1_UUID, "feature/feature-1", "PULL_REQUEST", "PULL_REQUEST");

insertBranch(MAIN_BRANCH_2, PROJECT_2_UUID, "trunk", "BRANCH", "LONG");
insertBranch(LONG_BRANCH_2, PROJECT_2_UUID, "branch-1", "BRANCH", "LONG");
insertBranch(SHORT_BRANCH_2, PROJECT_2_UUID, "feature/bar", "BRANCH", "SHORT");
insertBranch(PR_2, PROJECT_2_UUID, "feature/feature-2", "PULL_REQUEST", "PULL_REQUEST");
}

@Test
public void execute_migration() throws SQLException {
underTest.execute();

verifyMigrationResult();
}

@Test
public void migration_is_re_entrant() throws SQLException {
underTest.execute();
underTest.execute();

verifyMigrationResult();
}

private void verifyMigrationResult() {
assertThat(dbTester.select("select UUID from " + TABLE + " where EXCLUDE_FROM_PURGE = true")
.stream()
.map(e -> e.get("UUID"))
.collect(Collectors.toList())).containsExactlyInAnyOrder(MAIN_BRANCH_1, LONG_BRANCH_1, MAIN_BRANCH_2, LONG_BRANCH_2);
assertThat(dbTester.select("select UUID from " + TABLE + " where EXCLUDE_FROM_PURGE = false")
.stream()
.map(e -> e.get("UUID"))
.collect(Collectors.toList())).containsExactlyInAnyOrder(SHORT_BRANCH_1, SHORT_BRANCH_2, PR_1, PR_2);
}

private void insertBranch(String uuid, String projectUuid, String key, String keyType, String branchType) {
dbTester.executeInsert(
TABLE,
"UUID", uuid,
"PROJECT_UUID", projectUuid,
"KEE", key,
"BRANCH_TYPE", branchType,
"KEY_TYPE", keyType,
"MERGE_BRANCH_UUID", null,
"CREATED_AT", System2.INSTANCE.now(),
"UPDATED_AT", System2.INSTANCE.now());
}
}

+ 13
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v80/MakeExcludeBranchFromPurgeColumnNotNullableTest/schema.sql View File

CREATE TABLE PROJECT_BRANCHES(
UUID VARCHAR(50) NOT NULL,
PROJECT_UUID VARCHAR(50) NOT NULL,
KEE VARCHAR(255) NOT NULL,
BRANCH_TYPE VARCHAR(12),
MERGE_BRANCH_UUID VARCHAR(50),
KEY_TYPE VARCHAR(12) NOT NULL,
PULL_REQUEST_BINARY BLOB,
MANUAL_BASELINE_ANALYSIS_UUID VARCHAR(40),
CREATED_AT BIGINT NOT NULL,
UPDATED_AT BIGINT NOT NULL,
EXCLUDE_FROM_PURGE BOOLEAN
);

+ 12
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/AddExcludeBranchFromPurgeColumnTest/schema.sql View File

CREATE TABLE PROJECT_BRANCHES(
UUID VARCHAR(50) NOT NULL,
PROJECT_UUID VARCHAR(50) NOT NULL,
KEE VARCHAR(255) NOT NULL,
BRANCH_TYPE VARCHAR(12),
MERGE_BRANCH_UUID VARCHAR(50),
KEY_TYPE VARCHAR(12) NOT NULL,
PULL_REQUEST_BINARY BLOB,
MANUAL_BASELINE_ANALYSIS_UUID VARCHAR(40),
CREATED_AT BIGINT NOT NULL,
UPDATED_AT BIGINT NOT NULL
);

+ 11
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateDefaultBranchesToKeepSettingTest/schema.sql View File

CREATE TABLE "PROPERTIES" (
"ID" INTEGER NOT NULL AUTO_INCREMENT (1,1),
"PROP_KEY" VARCHAR(512) NOT NULL,
"RESOURCE_ID" INTEGER,
"USER_ID" INTEGER,
"IS_EMPTY" BOOLEAN NOT NULL,
"TEXT_VALUE" VARCHAR(4000),
"CLOB_VALUE" CLOB,
"CREATED_AT" BIGINT
);
CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY");

+ 13
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/PopulateExcludeBranchFromPurgeColumnTest/schema.sql View File

CREATE TABLE PROJECT_BRANCHES(
UUID VARCHAR(50) NOT NULL,
PROJECT_UUID VARCHAR(50) NOT NULL,
KEE VARCHAR(255) NOT NULL,
BRANCH_TYPE VARCHAR(12),
MERGE_BRANCH_UUID VARCHAR(50),
KEY_TYPE VARCHAR(12) NOT NULL,
PULL_REQUEST_BINARY BLOB,
MANUAL_BASELINE_ANALYSIS_UUID VARCHAR(40),
CREATED_AT BIGINT NOT NULL,
UPDATED_AT BIGINT NOT NULL,
EXCLUDE_FROM_PURGE BOOLEAN DEFAULT FALSE NOT NULL
);

+ 1
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/branch/ws/BranchWsModule.java View File

ListAction.class, ListAction.class,
DeleteAction.class, DeleteAction.class,
RenameAction.class, RenameAction.class,
SetAutomaticDeletionProtectionAction.class,
BranchesWs.class); BranchesWs.class);
} }
} }

+ 2
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/branch/ws/BranchesWs.java View File

static void addBranchParam(NewAction action) { static void addBranchParam(NewAction action) {
action action
.createParam(PARAM_BRANCH) .createParam(PARAM_BRANCH)
.setDescription("Name of the branch")
.setExampleValue("branch1")
.setDescription("Branch key")
.setExampleValue("feature/my_branch")
.setRequired(true); .setRequired(true);
} }



+ 1
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/branch/ws/ListAction.java View File

ofNullable(branchKey).ifPresent(builder::setName); ofNullable(branchKey).ifPresent(builder::setName);
builder.setIsMain(branch.isMain()); builder.setIsMain(branch.isMain());
builder.setType(Common.BranchType.valueOf(branch.getBranchType().name())); builder.setType(Common.BranchType.valueOf(branch.getBranchType().name()));
builder.setExcludedFromPurge(branch.isExcludeFromPurge());
return builder; return builder;
} }



+ 2
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/branch/ws/ProjectBranchesParameters.java View File

public static final String ACTION_LIST = "list"; public static final String ACTION_LIST = "list";
public static final String ACTION_DELETE = "delete"; public static final String ACTION_DELETE = "delete";
public static final String ACTION_RENAME = "rename"; public static final String ACTION_RENAME = "rename";
public static final String ACTION_SET_AUTOMATIC_DELETION_PROTECTION = "set_automatic_deletion_protection";


// parameters // parameters
public static final String PARAM_PROJECT = "project"; public static final String PARAM_PROJECT = "project";
public static final String PARAM_COMPONENT = "component"; public static final String PARAM_COMPONENT = "component";
public static final String PARAM_BRANCH = "branch"; public static final String PARAM_BRANCH = "branch";
public static final String PARAM_NAME = "name"; public static final String PARAM_NAME = "name";
public static final String PARAM_VALUE = "value";


private ProjectBranchesParameters() { private ProjectBranchesParameters() {
// static utility class // static utility class

+ 98
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/branch/ws/SetAutomaticDeletionProtectionAction.java View File

/*
* SonarQube
* Copyright (C) 2009-2019 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.branch.ws;

import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.web.UserRole;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.BranchDto;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.user.UserSession;

import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static org.sonar.server.branch.ws.BranchesWs.addBranchParam;
import static org.sonar.server.branch.ws.BranchesWs.addProjectParam;
import static org.sonar.server.branch.ws.ProjectBranchesParameters.ACTION_SET_AUTOMATIC_DELETION_PROTECTION;
import static org.sonar.server.branch.ws.ProjectBranchesParameters.PARAM_BRANCH;
import static org.sonar.server.branch.ws.ProjectBranchesParameters.PARAM_PROJECT;
import static org.sonar.server.branch.ws.ProjectBranchesParameters.PARAM_VALUE;

public class SetAutomaticDeletionProtectionAction implements BranchWsAction {

private final DbClient dbClient;
private final UserSession userSession;
private final ComponentFinder componentFinder;

public SetAutomaticDeletionProtectionAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder) {
this.dbClient = dbClient;
this.userSession = userSession;
this.componentFinder = componentFinder;
}

@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction(ACTION_SET_AUTOMATIC_DELETION_PROTECTION)
.setSince("8.1")
.setDescription("Protect a specific branch from automatic deletion. Protection can't be disabled for the main branch.<br/>"
+ "Requires 'Administer' permission on the specified project.")
.setPost(true)
.setHandler(this);

addProjectParam(action);
addBranchParam(action);
action.createParam(PARAM_VALUE)
.setRequired(true)
.setBooleanPossibleValues()
.setDescription("Sets whether the branch should be protected from automatic deletion when it hasn't been analyzed for a set period of time.")
.setExampleValue("true");
}

@Override
public void handle(Request request, Response response) throws Exception {
userSession.checkLoggedIn();
String projectKey = request.mandatoryParam(PARAM_PROJECT);
String branchKey = request.mandatoryParam(PARAM_BRANCH);
boolean excludeFromPurge = request.mandatoryParamAsBoolean(PARAM_VALUE);

try (DbSession dbSession = dbClient.openSession(false)) {
ComponentDto project = componentFinder.getRootComponentByUuidOrKey(dbSession, null, projectKey);
checkPermission(project);

BranchDto branch = dbClient.branchDao().selectByBranchKey(dbSession, project.uuid(), branchKey)
.orElseThrow(() -> new NotFoundException(format("Branch '%s' not found for project '%s'", branchKey, projectKey)));
checkArgument(!branch.isMain() || excludeFromPurge, "Main branch of the project is always excluded from automatic deletion.");

dbClient.branchDao().updateExcludeFromPurge(dbSession, branch.getUuid(), excludeFromPurge);
dbSession.commit();
response.noContent();
}

}

private void checkPermission(ComponentDto project) {
userSession.checkComponentPermission(UserRole.ADMIN, project);
}
}

+ 4
- 2
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/branch/ws/list-example.json View File

"status": { "status": {
"qualityGateStatus": "OK" "qualityGateStatus": "OK"
}, },
"analysisDate": "2017-04-03T13:37:00+0100"
"analysisDate": "2017-04-03T13:37:00+0100",
"excludedFromPurge": false
}, },
{ {
"name": "master", "name": "master",
"status": { "status": {
"qualityGateStatus": "ERROR" "qualityGateStatus": "ERROR"
}, },
"analysisDate": "2017-04-01T01:15:42+0100"
"analysisDate": "2017-04-01T01:15:42+0100",
"excludedFromPurge": false
} }
] ]
} }

+ 1
- 1
server/sonar-webserver-webapi/src/test/java/org/sonar/server/branch/ws/BranchWsModuleTest.java View File

public void verify_count_of_added_components() { public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer(); ComponentContainer container = new ComponentContainer();
new BranchWsModule().configure(container); new BranchWsModule().configure(container);
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 4);
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 5);
} }
} }

+ 208
- 0
server/sonar-webserver-webapi/src/test/java/org/sonar/server/branch/ws/SetAutomaticDeletionProtectionActionTest.java View File

/*
* SonarQube
* Copyright (C) 2009-2019 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.branch.ws;

import java.util.Optional;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.resources.ResourceTypes;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
import org.sonar.db.DbTester;
import org.sonar.db.component.BranchDto;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ResourceTypesRule;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsActionTester;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.resources.Qualifiers.PROJECT;

public class SetAutomaticDeletionProtectionActionTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public DbTester db = DbTester.create(System2.INSTANCE);
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();

private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT);
private ComponentFinder componentFinder = new ComponentFinder(db.getDbClient(), resourceTypes);
private WsActionTester tester = new WsActionTester(new SetAutomaticDeletionProtectionAction(db.getDbClient(), userSession, componentFinder));

@Test
public void test_definition() {
WebService.Action definition = tester.getDef();
assertThat(definition.key()).isEqualTo("set_automatic_deletion_protection");
assertThat(definition.isPost()).isTrue();
assertThat(definition.isInternal()).isFalse();
assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("project", "branch", "value");
assertThat(definition.since()).isEqualTo("8.1");
}

@Test
public void fail_if_missing_project_parameter() {
userSession.logIn();

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("The 'project' parameter is missing");

tester.newRequest().execute();
}

@Test
public void fail_if_missing_branch_parameter() {
userSession.logIn();

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("The 'branch' parameter is missing");

tester.newRequest()
.setParam("project", "projectName")
.execute();
}

@Test
public void fail_if_missing_value_parameter() {
userSession.logIn();

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("The 'value' parameter is missing");

tester.newRequest()
.setParam("project", "projectName")
.setParam("branch", "foobar")
.execute();
}

@Test
public void fail_if_not_logged_in() {
expectedException.expect(UnauthorizedException.class);
expectedException.expectMessage("Authentication is required");

tester.newRequest().execute();
}

@Test
public void fail_if_no_administer_permission() {
userSession.logIn();
ComponentDto project = db.components().insertMainBranch();

expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");

tester.newRequest()
.setParam("project", project.getKey())
.setParam("branch", "branch1")
.setParam("value", "true")
.execute();
}

@Test
public void fail_when_attempting_to_set_main_branch_as_included_in_purge() {
userSession.logIn();
ComponentDto project = db.components().insertMainBranch();
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("branch1").setExcludeFromPurge(false));
userSession.addProjectPermission(UserRole.ADMIN, project);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Main branch of the project is always excluded from automatic deletion.");

tester.newRequest()
.setParam("project", project.getKey())
.setParam("branch", "master")
.setParam("value", "false")
.execute();
}

@Test
public void set_purge_exclusion() {
userSession.logIn();
ComponentDto project = db.components().insertMainBranch();
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("branch1").setExcludeFromPurge(false));
userSession.addProjectPermission(UserRole.ADMIN, project);

tester.newRequest()
.setParam("project", project.getKey())
.setParam("branch", "branch1")
.setParam("value", "true")
.execute();

assertThat(db.countRowsOfTable("project_branches")).isEqualTo(2);
Optional<BranchDto> mainBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.uuid());
assertThat(mainBranch.get().getKey()).isEqualTo("master");
assertThat(mainBranch.get().isExcludeFromPurge()).isFalse();

Optional<BranchDto> branchDto = db.getDbClient().branchDao().selectByUuid(db.getSession(), branch.uuid());
assertThat(branchDto.get().getKey()).isEqualTo("branch1");
assertThat(branchDto.get().isExcludeFromPurge()).isTrue();
}

@Test
public void fail_on_non_boolean_value_parameter() {
userSession.logIn();
ComponentDto project = db.components().insertMainBranch();

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Value of parameter 'value' (foobar) must be one of: [true, false, yes, no]");

tester.newRequest()
.setParam("project", project.getKey())
.setParam("branch", "branch1")
.setParam("value", "foobar")
.execute();
}

@Test
public void fail_if_project_does_not_exist() {
userSession.logIn();

expectedException.expect(NotFoundException.class);
expectedException.expectMessage("Project key 'foo' not found");

tester.newRequest()
.setParam("project", "foo")
.setParam("branch", "branch1")
.setParam("value", "true")
.execute();
}

@Test
public void fail_if_branch_does_not_exist() {
userSession.logIn();
ComponentDto project = db.components().insertMainBranch();
userSession.addProjectPermission(UserRole.ADMIN, project);

expectedException.expect(NotFoundException.class);
expectedException.expectMessage("Branch 'branch1' not found for project '" + project.getKey() + "'");

tester.newRequest()
.setParam("project", project.getKey())
.setParam("branch", "branch1")
.setParam("value", "true")
.execute();
}
}

+ 2
- 1
sonar-core/src/main/java/org/sonar/core/config/PurgeConstants.java View File

String WEEKS_BEFORE_KEEPING_ONLY_ANALYSES_WITH_VERSION = "sonar.dbcleaner.weeksBeforeKeepingOnlyAnalysesWithVersion"; String WEEKS_BEFORE_KEEPING_ONLY_ANALYSES_WITH_VERSION = "sonar.dbcleaner.weeksBeforeKeepingOnlyAnalysesWithVersion";
String WEEKS_BEFORE_DELETING_ALL_SNAPSHOTS = "sonar.dbcleaner.weeksBeforeDeletingAllSnapshots"; String WEEKS_BEFORE_DELETING_ALL_SNAPSHOTS = "sonar.dbcleaner.weeksBeforeDeletingAllSnapshots";
String DAYS_BEFORE_DELETING_CLOSED_ISSUES = "sonar.dbcleaner.daysBeforeDeletingClosedIssues"; String DAYS_BEFORE_DELETING_CLOSED_ISSUES = "sonar.dbcleaner.daysBeforeDeletingClosedIssues";
String DAYS_BEFORE_DELETING_INACTIVE_SHORT_LIVING_BRANCHES = "sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches";
String DAYS_BEFORE_DELETING_INACTIVE_BRANCHES = "sonar.dbcleaner.daysBeforeDeletingInactiveBranches";
String BRANCHES_TO_KEEP_WHEN_INACTIVE = "sonar.dbcleaner.branchesToKeepWhenInactive";
} }

+ 13
- 15
sonar-core/src/main/java/org/sonar/core/config/PurgeProperties.java View File

+ "the DbCleaner keeps the most recent one and fully deletes the other ones.") + "the DbCleaner keeps the most recent one and fully deletes the other ones.")
.type(PropertyType.INTEGER) .type(PropertyType.INTEGER)
.onQualifiers(Qualifiers.PROJECT) .onQualifiers(Qualifiers.PROJECT)
.category(CoreProperties.CATEGORY_GENERAL)
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER)
.category(CoreProperties.CATEGORY_HOUSEKEEPING)
.subCategory(CoreProperties.SUBCATEGORY_GENERAL)
.index(1) .index(1)
.build(), .build(),


+ "the DbCleaner keeps the most recent one and fully deletes the other ones") + "the DbCleaner keeps the most recent one and fully deletes the other ones")
.type(PropertyType.INTEGER) .type(PropertyType.INTEGER)
.onQualifiers(Qualifiers.PROJECT) .onQualifiers(Qualifiers.PROJECT)
.category(CoreProperties.CATEGORY_GENERAL)
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER)
.category(CoreProperties.CATEGORY_HOUSEKEEPING)
.subCategory(CoreProperties.SUBCATEGORY_GENERAL)
.index(2) .index(2)
.build(), .build(),


+ "the DbCleaner keeps the most recent one and fully deletes the other ones.") + "the DbCleaner keeps the most recent one and fully deletes the other ones.")
.type(PropertyType.INTEGER) .type(PropertyType.INTEGER)
.onQualifiers(Qualifiers.PROJECT) .onQualifiers(Qualifiers.PROJECT)
.category(CoreProperties.CATEGORY_GENERAL)
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER)
.category(CoreProperties.CATEGORY_HOUSEKEEPING)
.subCategory(CoreProperties.SUBCATEGORY_GENERAL)
.index(3) .index(3)
.build(), .build(),



PropertyDefinition.builder(PurgeConstants.WEEKS_BEFORE_KEEPING_ONLY_ANALYSES_WITH_VERSION) PropertyDefinition.builder(PurgeConstants.WEEKS_BEFORE_KEEPING_ONLY_ANALYSES_WITH_VERSION)
.defaultValue("104") .defaultValue("104")
.name("Keep only analyses with a version event after") .name("Keep only analyses with a version event after")
.description("After this number of weeks, the DbCleaner keeps only analyses with a version event associated.") .description("After this number of weeks, the DbCleaner keeps only analyses with a version event associated.")
.type(PropertyType.INTEGER) .type(PropertyType.INTEGER)
.onQualifiers(Qualifiers.PROJECT) .onQualifiers(Qualifiers.PROJECT)
.category(CoreProperties.CATEGORY_GENERAL)
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER)
.category(CoreProperties.CATEGORY_HOUSEKEEPING)
.subCategory(CoreProperties.SUBCATEGORY_GENERAL)
.index(4) .index(4)
.build(), .build(),


.description("After this number of weeks, all analyses are fully deleted.") .description("After this number of weeks, all analyses are fully deleted.")
.type(PropertyType.INTEGER) .type(PropertyType.INTEGER)
.onQualifiers(Qualifiers.PROJECT) .onQualifiers(Qualifiers.PROJECT)
.category(CoreProperties.CATEGORY_GENERAL)
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER)
.category(CoreProperties.CATEGORY_HOUSEKEEPING)
.subCategory(CoreProperties.SUBCATEGORY_GENERAL)
.index(5) .index(5)
.build(), .build(),


.description("Issues that have been closed for more than this number of days will be deleted.") .description("Issues that have been closed for more than this number of days will be deleted.")
.type(PropertyType.INTEGER) .type(PropertyType.INTEGER)
.onQualifiers(Qualifiers.PROJECT) .onQualifiers(Qualifiers.PROJECT)
.category(CoreProperties.CATEGORY_GENERAL)
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER)
.category(CoreProperties.CATEGORY_HOUSEKEEPING)
.subCategory(CoreProperties.SUBCATEGORY_GENERAL)
.index(6) .index(6)
.build()
);
.build());
} }
} }

+ 3
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

property.error.notRegexp=Not a valid Java regular expression property.error.notRegexp=Not a valid Java regular expression
property.error.notInOptions=Not a valid option property.error.notInOptions=Not a valid option
property.category.scm=SCM property.category.scm=SCM
property.category.housekeeping=Housekeeping
property.category.housekeeping.general=General
property.category.housekeeping.branchesAndPullRequests=Branches and Pull Requests
property.sonar.branch.longLivedBranches.regex.description=Regular expression used to detect whether a branch is a long living branch (as opposed to short living branch), based on its name. This applies only during first analysis, the type of a branch cannot be changed later. property.sonar.branch.longLivedBranches.regex.description=Regular expression used to detect whether a branch is a long living branch (as opposed to short living branch), based on its name. This applies only during first analysis, the type of a branch cannot be changed later.





+ 13
- 1
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java View File



/** /**
* @since 4.0 * @since 4.0
* @deprecated since 8.1. Database cleaning now has a dedicated category {@link CoreProperties#CATEGORY_HOUSEKEEPING}.
*/ */
@Deprecated
String SUBCATEGORY_DATABASE_CLEANER = "databaseCleaner"; String SUBCATEGORY_DATABASE_CLEANER = "databaseCleaner";


/** /**
*/ */
String SUBCATEGORY_DUPLICATIONS = "duplications"; String SUBCATEGORY_DUPLICATIONS = "duplications";


/**
* @since 8.1
*/
String CATEGORY_HOUSEKEEPING = "housekeeping";

/** /**
* @since 6.6 * @since 6.6
*/ */
String SUBCATEGORY_BRANCHES = "Branches";
String SUBCATEGORY_BRANCHES_AND_PULL_REQUESTS = "branchesAndPullRequests";

/**
* @since 8.1
*/
String SUBCATEGORY_GENERAL = "general";


/** /**
* @since 4.0 * @since 4.0

+ 1
- 0
sonar-ws/src/main/protobuf/ws-projectbranches.proto View File

optional Status status = 5; optional Status status = 5;
optional bool isOrphan = 6; optional bool isOrphan = 6;
optional string analysisDate = 7; optional string analysisDate = 7;
optional bool excludedFromPurge = 8;
} }


message Status { message Status {

Loading…
Cancel
Save