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:
Michael Hoennig
2026-07-08 22:35:31 +02:00
co-authored by Claude Fable 5
parent b104eeee05
commit d0b38c557a
10 changed files with 246 additions and 9 deletions
@@ -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 }