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); | ||||
} | } |
@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 + | |||||
'}'; | |||||
} | } | ||||
} | } |
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); | |||||
} | } |
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 |
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)) { |
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); |
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 |
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 < #{toDate} | and pb.updated_at < #{toDate} | ||||
</select> | </select> | ||||
"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"); |
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[][] { |
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 |
@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 |
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()); | ||||
.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); | |||||
} | } | ||||
} | } |
/* | |||||
* 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()); | |||||
} | |||||
} |
.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); | |||||
} | } | ||||
} | } |
/* | |||||
* 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; | |||||
}); | |||||
} | |||||
} |
/* | |||||
* 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(); | |||||
} | |||||
} |
/* | |||||
* 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()); | |||||
} | |||||
} |
@Test | @Test | ||||
public void verify_migration_count() { | public void verify_migration_count() { | ||||
verifyMigrationCount(underTest, 7); | |||||
verifyMigrationCount(underTest, 10); | |||||
} | } | ||||
} | } |
/* | |||||
* 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"); | |||||
} | |||||
} |
/* | |||||
* 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()); | |||||
} | |||||
} |
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 | |||||
); |
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 | |||||
); |
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"); |
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 | |||||
); |
ListAction.class, | ListAction.class, | ||||
DeleteAction.class, | DeleteAction.class, | ||||
RenameAction.class, | RenameAction.class, | ||||
SetAutomaticDeletionProtectionAction.class, | |||||
BranchesWs.class); | BranchesWs.class); | ||||
} | } | ||||
} | } |
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); | ||||
} | } | ||||
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; | ||||
} | } | ||||
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 |
/* | |||||
* 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); | |||||
} | |||||
} |
"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 | |||||
} | } | ||||
] | ] | ||||
} | } |
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); | |||||
} | } | ||||
} | } |
/* | |||||
* 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(); | |||||
} | |||||
} |
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"; | |||||
} | } |
+ "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()); | |||||
} | } | ||||
} | } |
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. | ||||
/** | /** | ||||
* @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 |
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 { |