@@ -63,6 +63,11 @@ public class BranchDao implements Dao { | |||
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) { | |||
return selectByKey(dbSession, projectUuid, key, KeyType.BRANCH); | |||
} |
@@ -88,6 +88,8 @@ public class BranchDto { | |||
@Nullable | |||
private byte[] pullRequestBinary; | |||
private boolean excludeFromPurge; | |||
public String getUuid() { | |||
return uuid; | |||
} | |||
@@ -171,6 +173,14 @@ public class BranchDto { | |||
return decodePullRequestData(pullRequestBinary); | |||
} | |||
public boolean isExcludeFromPurge() { | |||
return excludeFromPurge; | |||
} | |||
public void setExcludeFromPurge(boolean excludeFromPurge) { | |||
this.excludeFromPurge = excludeFromPurge; | |||
} | |||
private static byte[] encodePullRequestData(DbProjectBranches.PullRequestData pullRequestData) { | |||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
try { | |||
@@ -212,14 +222,14 @@ public class BranchDto { | |||
@Override | |||
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 + | |||
'}'; | |||
} | |||
} |
@@ -34,6 +34,9 @@ public interface BranchMapper { | |||
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 selectByUuid(@Param("uuid") String uuid); | |||
@@ -44,5 +47,5 @@ public interface BranchMapper { | |||
long countNonMainBranches(); | |||
long countByTypeAndCreationDate(@Param("branchType")String branchType, @Param("sinceDate") long sinceDate); | |||
long countByTypeAndCreationDate(@Param("branchType") String branchType, @Param("sinceDate") long sinceDate); | |||
} |
@@ -38,24 +38,24 @@ public class PurgeConfiguration { | |||
private final String projectUuid; | |||
private final Collection<String> scopesWithoutHistoricalData; | |||
private final int maxAgeInDaysOfClosedIssues; | |||
private final Optional<Integer> maxAgeInDaysOfInactiveShortLivingBranches; | |||
private final Optional<Integer> maxAgeInDaysOfInactiveBranches; | |||
private final System2 system2; | |||
private final Set<String> disabledComponentUuids; | |||
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.projectUuid = projectUuid; | |||
this.scopesWithoutHistoricalData = scopesWithoutHistoricalData; | |||
this.maxAgeInDaysOfClosedIssues = maxAgeInDaysOfClosedIssues; | |||
this.system2 = system2; | |||
this.disabledComponentUuids = disabledComponentUuids; | |||
this.maxAgeInDaysOfInactiveShortLivingBranches = maxAgeInDaysOfInactiveShortLivingBranches; | |||
this.maxAgeInDaysOfInactiveBranches = maxAgeInDaysOfInactiveBranches; | |||
} | |||
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(), | |||
config.getInt(PurgeConstants.DAYS_BEFORE_DELETING_INACTIVE_SHORT_LIVING_BRANCHES), System2.INSTANCE, disabledComponentUuids); | |||
config.getInt(PurgeConstants.DAYS_BEFORE_DELETING_INACTIVE_BRANCHES), System2.INSTANCE, disabledComponentUuids); | |||
} | |||
/** | |||
@@ -87,8 +87,8 @@ public class PurgeConfiguration { | |||
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 |
@@ -41,6 +41,7 @@ import org.sonar.db.component.ComponentTreeQuery; | |||
import org.sonar.db.component.ComponentTreeQuery.Strategy; | |||
import static java.util.Collections.emptyList; | |||
import static java.util.Optional.ofNullable; | |||
import static org.sonar.api.utils.DateUtils.dateToLong; | |||
import static org.sonar.db.DatabaseUtils.executeLargeInputs; | |||
@@ -75,14 +76,15 @@ public class PurgeDao implements Dao { | |||
} | |||
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()) { | |||
// not available if branch plugin is not installed | |||
return; | |||
} | |||
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) { | |||
if (!rootUuid.equals(branchUuid)) { |
@@ -94,7 +94,7 @@ public interface PurgeMapper { | |||
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 | |||
String selectSpecificAnalysisNewCodePeriod(@Param("projectUuid") String projectUuid); |
@@ -9,7 +9,8 @@ | |||
pb.key_type as keyType, | |||
pb.branch_type as branchType, | |||
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> | |||
<insert id="insert" parameterType="map" useGeneratedKeys="false"> | |||
@@ -22,7 +23,8 @@ | |||
merge_branch_uuid, | |||
pull_request_binary, | |||
created_at, | |||
updated_at | |||
updated_at, | |||
exclude_from_purge | |||
) values ( | |||
#{dto.uuid, jdbcType=VARCHAR}, | |||
#{dto.projectUuid, jdbcType=VARCHAR}, | |||
@@ -32,7 +34,8 @@ | |||
#{dto.mergeBranchUuid, jdbcType=VARCHAR}, | |||
#{dto.pullRequestBinary, jdbcType=BINARY}, | |||
#{now, jdbcType=BIGINT}, | |||
#{now, jdbcType=BIGINT} | |||
#{now, jdbcType=BIGINT}, | |||
#{dto.excludeFromPurge, jdbcType=BOOLEAN} | |||
) | |||
</insert> | |||
@@ -45,6 +48,15 @@ | |||
uuid = #{projectUuid, jdbcType=VARCHAR} | |||
</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 project_branches | |||
set |
@@ -47,14 +47,14 @@ | |||
AND ncp.branch_uuid=#{projectUuid,jdbcType=VARCHAR} | |||
</select> | |||
<select id="selectStaleShortLivingBranchesAndPullRequests" parameterType="map" resultType="String"> | |||
<select id="selectStaleBranchesAndPullRequests" parameterType="map" resultType="String"> | |||
select | |||
pb.uuid | |||
from | |||
project_branches pb | |||
where | |||
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} | |||
</select> | |||
@@ -591,7 +591,8 @@ CREATE TABLE "PROJECT_BRANCHES"( | |||
"PULL_REQUEST_BINARY" BLOB, | |||
"MANUAL_BASELINE_ANALYSIS_UUID" VARCHAR(40), | |||
"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"); | |||
CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE_KEY_TYPE" ON "PROJECT_BRANCHES"("PROJECT_UUID", "KEE", "KEY_TYPE"); |
@@ -101,6 +101,22 @@ public class BranchDaoTest { | |||
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 | |||
public static Object[][] nullOrEmpty() { | |||
return new Object[][] { |
@@ -56,9 +56,10 @@ public class BranchDtoTest { | |||
underTest.setKey("K1"); | |||
underTest.setBranchType(BranchType.LONG); | |||
underTest.setMergeBranchUuid("U3"); | |||
underTest.setExcludeFromPurge(true); | |||
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 |
@@ -58,15 +58,15 @@ public class PurgeConfigurationTest { | |||
@Test | |||
public void should_have_empty_branch_purge_date() { | |||
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(); | |||
assertThat(conf.maxLiveDateOfInactiveShortLivingBranches().get().getTime()).isBetween(tenDaysAgo - 5000, tenDaysAgo + 5000); | |||
assertThat(conf.maxLiveDateOfInactiveBranches().get().getTime()).isBetween(tenDaysAgo - 5000, tenDaysAgo + 5000); | |||
} | |||
@Test | |||
public void should_calculate_branch_purge_date() { | |||
PurgeConfiguration conf = new PurgeConfiguration("root", "project", emptySet(), 30, Optional.empty(), System2.INSTANCE, emptySet()); | |||
assertThat(conf.maxLiveDateOfInactiveShortLivingBranches()).isEmpty(); | |||
assertThat(conf.maxLiveDateOfInactiveBranches()).isEmpty(); | |||
} | |||
@Test |
@@ -127,7 +127,7 @@ public class PurgeDaoTest { | |||
public void purge_failed_ce_tasks() { | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
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)); | |||
underTest.purge(dbSession, newConfigurationWith30Days(project.uuid()), PurgeListener.EMPTY, new PurgeProfiler()); | |||
@@ -137,27 +137,27 @@ public class PurgeDaoTest { | |||
} | |||
@Test | |||
public void purge_inactive_short_living_branches() { | |||
public void purge_inactive_branches() { | |||
when(system2.now()).thenReturn(new Date().getTime()); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
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()); | |||
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 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()); | |||
dbSession.commit(); | |||
assertThat(uuidsIn("projects")).containsOnly(project.uuid(), longBranch.uuid(), recentShortBranch.uuid()); | |||
assertThat(uuidsIn("projects")).containsOnly(project.uuid(), branch1.uuid(), branch2.uuid()); | |||
} | |||
@Test | |||
@@ -185,7 +185,7 @@ public class PurgeDaoTest { | |||
} | |||
@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()); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
ComponentDto project = db.components().insertMainBranch(); | |||
@@ -193,22 +193,22 @@ public class PurgeDaoTest { | |||
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 | |||
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(); | |||
// 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 | |||
@@ -483,8 +483,7 @@ public class PurgeDaoTest { | |||
.setProjectUuid(project1.uuid()) | |||
.setBranchUuid(project1.uuid()) | |||
.setType(NewCodePeriodType.SPECIFIC_ANALYSIS) | |||
.setValue(analysis1.getUuid()) | |||
); | |||
.setValue(analysis1.getUuid())); | |||
ComponentDto project2 = db.components().insertPrivateProject(); | |||
SnapshotDto analysis2 = db.components().insertSnapshot(newSnapshot() | |||
.setComponentUuid(project2.uuid()) | |||
@@ -510,8 +509,7 @@ public class PurgeDaoTest { | |||
.setProjectUuid(project.uuid()) | |||
.setBranchUuid(project.uuid()) | |||
.setType(NewCodePeriodType.SPECIFIC_ANALYSIS) | |||
.setValue(analysisProject.getUuid()) | |||
); | |||
.setValue(analysisProject.getUuid())); | |||
ComponentDto branch1 = db.components().insertProjectBranch(project); | |||
SnapshotDto analysisBranch1 = db.components().insertSnapshot(newSnapshot() | |||
.setComponentUuid(branch1.uuid()) | |||
@@ -527,8 +525,7 @@ public class PurgeDaoTest { | |||
.setProjectUuid(project.uuid()) | |||
.setBranchUuid(branch2.uuid()) | |||
.setType(NewCodePeriodType.SPECIFIC_ANALYSIS) | |||
.setValue(analysisBranch2.getUuid()) | |||
); | |||
.setValue(analysisBranch2.getUuid())); | |||
dbSession.commit(); | |||
assertThat(underTest.selectPurgeableAnalyses(project.uuid(), dbSession)) | |||
@@ -1178,8 +1175,8 @@ public class PurgeDaoTest { | |||
@Test | |||
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(); | |||
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> belongsToBranch1 = t -> t.setMainComponentUuid(project1.uuid()).setComponentUuid(branch1.uuid()); | |||
@@ -35,7 +35,6 @@ public class DbVersion80 implements DbVersion { | |||
.add(3006, "Create NEW_CODE_PERIOD table", CreateNewCodePeriodTable.class) | |||
.add(3007, "Populate NEW_CODE_PERIOD table", PopulateNewCodePeriodTable.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); | |||
} | |||
} |
@@ -0,0 +1,49 @@ | |||
/* | |||
* 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()); | |||
} | |||
} |
@@ -32,6 +32,9 @@ public class DbVersion81 implements DbVersion { | |||
.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(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); | |||
} | |||
} |
@@ -0,0 +1,106 @@ | |||
/* | |||
* 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; | |||
}); | |||
} | |||
} |
@@ -0,0 +1,45 @@ | |||
/* | |||
* 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(); | |||
} | |||
} |
@@ -0,0 +1,94 @@ | |||
/* | |||
* 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()); | |||
} | |||
} |
@@ -37,7 +37,7 @@ public class DbVersion81Test { | |||
@Test | |||
public void verify_migration_count() { | |||
verifyMigrationCount(underTest, 7); | |||
verifyMigrationCount(underTest, 10); | |||
} | |||
} |
@@ -0,0 +1,165 @@ | |||
/* | |||
* 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"); | |||
} | |||
} |
@@ -0,0 +1,109 @@ | |||
/* | |||
* 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()); | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
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 | |||
); |
@@ -0,0 +1,12 @@ | |||
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 | |||
); |
@@ -0,0 +1,11 @@ | |||
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"); |
@@ -0,0 +1,13 @@ | |||
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 | |||
); |
@@ -28,6 +28,7 @@ public class BranchWsModule extends Module { | |||
ListAction.class, | |||
DeleteAction.class, | |||
RenameAction.class, | |||
SetAutomaticDeletionProtectionAction.class, | |||
BranchesWs.class); | |||
} | |||
} |
@@ -54,8 +54,8 @@ public class BranchesWs implements WebService { | |||
static void addBranchParam(NewAction action) { | |||
action | |||
.createParam(PARAM_BRANCH) | |||
.setDescription("Name of the branch") | |||
.setExampleValue("branch1") | |||
.setDescription("Branch key") | |||
.setExampleValue("feature/my_branch") | |||
.setRequired(true); | |||
} | |||
@@ -141,6 +141,7 @@ public class ListAction implements BranchWsAction { | |||
ofNullable(branchKey).ifPresent(builder::setName); | |||
builder.setIsMain(branch.isMain()); | |||
builder.setType(Common.BranchType.valueOf(branch.getBranchType().name())); | |||
builder.setExcludedFromPurge(branch.isExcludeFromPurge()); | |||
return builder; | |||
} | |||
@@ -27,12 +27,14 @@ public class ProjectBranchesParameters { | |||
public static final String ACTION_LIST = "list"; | |||
public static final String ACTION_DELETE = "delete"; | |||
public static final String ACTION_RENAME = "rename"; | |||
public static final String ACTION_SET_AUTOMATIC_DELETION_PROTECTION = "set_automatic_deletion_protection"; | |||
// parameters | |||
public static final String PARAM_PROJECT = "project"; | |||
public static final String PARAM_COMPONENT = "component"; | |||
public static final String PARAM_BRANCH = "branch"; | |||
public static final String PARAM_NAME = "name"; | |||
public static final String PARAM_VALUE = "value"; | |||
private ProjectBranchesParameters() { | |||
// static utility class |
@@ -0,0 +1,98 @@ | |||
/* | |||
* 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); | |||
} | |||
} |
@@ -7,7 +7,8 @@ | |||
"status": { | |||
"qualityGateStatus": "OK" | |||
}, | |||
"analysisDate": "2017-04-03T13:37:00+0100" | |||
"analysisDate": "2017-04-03T13:37:00+0100", | |||
"excludedFromPurge": false | |||
}, | |||
{ | |||
"name": "master", | |||
@@ -16,7 +17,8 @@ | |||
"status": { | |||
"qualityGateStatus": "ERROR" | |||
}, | |||
"analysisDate": "2017-04-01T01:15:42+0100" | |||
"analysisDate": "2017-04-01T01:15:42+0100", | |||
"excludedFromPurge": false | |||
} | |||
] | |||
} |
@@ -30,6 +30,6 @@ public class BranchWsModuleTest { | |||
public void verify_count_of_added_components() { | |||
ComponentContainer container = new ComponentContainer(); | |||
new BranchWsModule().configure(container); | |||
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 4); | |||
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 5); | |||
} | |||
} |
@@ -0,0 +1,208 @@ | |||
/* | |||
* 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(); | |||
} | |||
} |
@@ -27,5 +27,6 @@ public interface PurgeConstants { | |||
String WEEKS_BEFORE_KEEPING_ONLY_ANALYSES_WITH_VERSION = "sonar.dbcleaner.weeksBeforeKeepingOnlyAnalysesWithVersion"; | |||
String WEEKS_BEFORE_DELETING_ALL_SNAPSHOTS = "sonar.dbcleaner.weeksBeforeDeletingAllSnapshots"; | |||
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"; | |||
} |
@@ -41,8 +41,8 @@ public final class PurgeProperties { | |||
+ "the DbCleaner keeps the most recent one and fully deletes the other ones.") | |||
.type(PropertyType.INTEGER) | |||
.onQualifiers(Qualifiers.PROJECT) | |||
.category(CoreProperties.CATEGORY_GENERAL) | |||
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER) | |||
.category(CoreProperties.CATEGORY_HOUSEKEEPING) | |||
.subCategory(CoreProperties.SUBCATEGORY_GENERAL) | |||
.index(1) | |||
.build(), | |||
@@ -53,8 +53,8 @@ public final class PurgeProperties { | |||
+ "the DbCleaner keeps the most recent one and fully deletes the other ones") | |||
.type(PropertyType.INTEGER) | |||
.onQualifiers(Qualifiers.PROJECT) | |||
.category(CoreProperties.CATEGORY_GENERAL) | |||
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER) | |||
.category(CoreProperties.CATEGORY_HOUSEKEEPING) | |||
.subCategory(CoreProperties.SUBCATEGORY_GENERAL) | |||
.index(2) | |||
.build(), | |||
@@ -65,20 +65,19 @@ public final class PurgeProperties { | |||
+ "the DbCleaner keeps the most recent one and fully deletes the other ones.") | |||
.type(PropertyType.INTEGER) | |||
.onQualifiers(Qualifiers.PROJECT) | |||
.category(CoreProperties.CATEGORY_GENERAL) | |||
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER) | |||
.category(CoreProperties.CATEGORY_HOUSEKEEPING) | |||
.subCategory(CoreProperties.SUBCATEGORY_GENERAL) | |||
.index(3) | |||
.build(), | |||
PropertyDefinition.builder(PurgeConstants.WEEKS_BEFORE_KEEPING_ONLY_ANALYSES_WITH_VERSION) | |||
.defaultValue("104") | |||
.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.") | |||
.type(PropertyType.INTEGER) | |||
.onQualifiers(Qualifiers.PROJECT) | |||
.category(CoreProperties.CATEGORY_GENERAL) | |||
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER) | |||
.category(CoreProperties.CATEGORY_HOUSEKEEPING) | |||
.subCategory(CoreProperties.SUBCATEGORY_GENERAL) | |||
.index(4) | |||
.build(), | |||
@@ -88,8 +87,8 @@ public final class PurgeProperties { | |||
.description("After this number of weeks, all analyses are fully deleted.") | |||
.type(PropertyType.INTEGER) | |||
.onQualifiers(Qualifiers.PROJECT) | |||
.category(CoreProperties.CATEGORY_GENERAL) | |||
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER) | |||
.category(CoreProperties.CATEGORY_HOUSEKEEPING) | |||
.subCategory(CoreProperties.SUBCATEGORY_GENERAL) | |||
.index(5) | |||
.build(), | |||
@@ -99,10 +98,9 @@ public final class PurgeProperties { | |||
.description("Issues that have been closed for more than this number of days will be deleted.") | |||
.type(PropertyType.INTEGER) | |||
.onQualifiers(Qualifiers.PROJECT) | |||
.category(CoreProperties.CATEGORY_GENERAL) | |||
.subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER) | |||
.category(CoreProperties.CATEGORY_HOUSEKEEPING) | |||
.subCategory(CoreProperties.SUBCATEGORY_GENERAL) | |||
.index(6) | |||
.build() | |||
); | |||
.build()); | |||
} | |||
} |
@@ -1034,6 +1034,9 @@ property.error.notFloat=Not a floating point number | |||
property.error.notRegexp=Not a valid Java regular expression | |||
property.error.notInOptions=Not a valid option | |||
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. | |||
@@ -41,7 +41,9 @@ public interface CoreProperties { | |||
/** | |||
* @since 4.0 | |||
* @deprecated since 8.1. Database cleaning now has a dedicated category {@link CoreProperties#CATEGORY_HOUSEKEEPING}. | |||
*/ | |||
@Deprecated | |||
String SUBCATEGORY_DATABASE_CLEANER = "databaseCleaner"; | |||
/** | |||
@@ -54,10 +56,20 @@ public interface CoreProperties { | |||
*/ | |||
String SUBCATEGORY_DUPLICATIONS = "duplications"; | |||
/** | |||
* @since 8.1 | |||
*/ | |||
String CATEGORY_HOUSEKEEPING = "housekeeping"; | |||
/** | |||
* @since 6.6 | |||
*/ | |||
String SUBCATEGORY_BRANCHES = "Branches"; | |||
String SUBCATEGORY_BRANCHES_AND_PULL_REQUESTS = "branchesAndPullRequests"; | |||
/** | |||
* @since 8.1 | |||
*/ | |||
String SUBCATEGORY_GENERAL = "general"; | |||
/** | |||
* @since 4.0 |
@@ -44,6 +44,7 @@ message Branch { | |||
optional Status status = 5; | |||
optional bool isOrphan = 6; | |||
optional string analysisDate = 7; | |||
optional bool excludedFromPurge = 8; | |||
} | |||
message Status { |