added age-based build retention: artifacts.retentionMaxAge (e.g. 30d, empty = no limit) drops builds older than the given age; combines with retentionPerBranch as independent caps — a build is kept only while it satisfies both limits; a branch's newest build is never age-pruned and keepLatestGreen now shields the latest green build from both limits, keeping the permanent /branches/... links valid
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b104eeee05
commit
d0b38c557a
@@ -1,5 +1,7 @@
|
||||
package de.hoennig.gittally.build
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
interface BuildResultRepository {
|
||||
fun append(result: BuildResult)
|
||||
|
||||
@@ -37,13 +39,16 @@ interface BuildResultRepository {
|
||||
|
||||
/**
|
||||
* Keeps the newest [retentionPerBranch] entries per branch and drops entries of branches
|
||||
* not contained in [originBranches]. With [keepLatestGreen], the newest SUCCESS entry of
|
||||
* each surviving branch is kept even beyond the retention count, so the permanent
|
||||
* not contained in [originBranches]. With [retentionCutoff], entries started before the
|
||||
* cutoff are dropped even within the retention count — except each branch's newest entry,
|
||||
* so dormant branches keep their last status. With [keepLatestGreen], the newest SUCCESS
|
||||
* entry of each surviving branch is kept even beyond both limits, so the permanent
|
||||
* `/branches/…` artifact links stay valid while newer builds fail. Returns the removed entries.
|
||||
*/
|
||||
fun prune(
|
||||
originBranches: Collection<String>,
|
||||
retentionPerBranch: Int,
|
||||
keepLatestGreen: Boolean = false,
|
||||
retentionCutoff: Instant? = null,
|
||||
): List<BuildResult>
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Stores build results as a JSON file, e.g. `.git/gittally/build-results.json`.
|
||||
@@ -123,6 +124,7 @@ class FileBuildResultRepository(
|
||||
originBranches: Collection<String>,
|
||||
retentionPerBranch: Int,
|
||||
keepLatestGreen: Boolean,
|
||||
retentionCutoff: Instant?,
|
||||
): List<BuildResult> {
|
||||
synchronized(lock) {
|
||||
val results = load()
|
||||
@@ -137,6 +139,10 @@ class FileBuildResultRepository(
|
||||
entries
|
||||
.sortedByDescending { it.startedAt }
|
||||
.take(retentionPerBranch.coerceAtLeast(0))
|
||||
.filterIndexed { index, entry ->
|
||||
// the branch's newest entry is never age-pruned
|
||||
index == 0 || retentionCutoff == null || !entry.startedAt.isBefore(retentionCutoff)
|
||||
}
|
||||
val latestGreen =
|
||||
entries
|
||||
.filter { keepLatestGreen && it.status == BuildStatus.SUCCESS }
|
||||
|
||||
@@ -156,7 +156,10 @@ class InitCommand(
|
||||
rootDir: ""
|
||||
# number of builds to keep per branch
|
||||
retentionPerBranch: 3
|
||||
# keep each branch's latest green build beyond the retention count,
|
||||
# additionally drop builds older than this age (h/d suffix, e.g. 30d); empty = no age limit;
|
||||
# a branch's newest build is never age-pruned
|
||||
retentionMaxAge: ""
|
||||
# keep each branch's latest green build beyond the retention limits,
|
||||
# so the permanent /branches/<branch-key>/... artifact URLs stay valid while newer builds fail
|
||||
keepLatestGreen: true
|
||||
|
||||
|
||||
@@ -71,9 +71,17 @@ data class BuildsConfig(
|
||||
data class ArtifactsConfig(
|
||||
val retentionPerBranch: Int = 3,
|
||||
/**
|
||||
* Keep each branch's latest green (SUCCESS) build beyond [retentionPerBranch],
|
||||
* so the permanent `/branches/<branch-key>/…` artifact URLs stay valid while newer
|
||||
* builds fail; the build is still dropped once its branch is gone from origin.
|
||||
* Additionally drop builds older than this age (e.g. `30d` or `12h`); empty means no
|
||||
* age limit. Combines with [retentionPerBranch] — a build is kept only while it
|
||||
* satisfies both limits. A branch's newest build is never age-pruned, so dormant
|
||||
* branches keep their last status.
|
||||
*/
|
||||
val retentionMaxAge: String = "",
|
||||
/**
|
||||
* Keep each branch's latest green (SUCCESS) build beyond [retentionPerBranch] and
|
||||
* [retentionMaxAge], so the permanent `/branches/<branch-key>/…` artifact URLs stay
|
||||
* valid while newer builds fail; the build is still dropped once its branch is gone
|
||||
* from origin.
|
||||
*/
|
||||
val keepLatestGreen: Boolean = true,
|
||||
/**
|
||||
|
||||
@@ -250,7 +250,16 @@ class Watcher(
|
||||
originBranches: List<String>,
|
||||
workingDir: Path,
|
||||
) {
|
||||
repository.prune(originBranches, config.artifacts.retentionPerBranch, config.artifacts.keepLatestGreen)
|
||||
val retentionCutoff =
|
||||
config.artifacts.retentionMaxAge
|
||||
.takeIf { it.isNotBlank() }
|
||||
?.let { clock.instant().minus(DurationParser.parse(it)) }
|
||||
repository.prune(
|
||||
originBranches,
|
||||
config.artifacts.retentionPerBranch,
|
||||
config.artifacts.keepLatestGreen,
|
||||
retentionCutoff,
|
||||
)
|
||||
artifactStore.prune(repository.history())
|
||||
pruneWorktrees(originBranches, workingDir)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user