feat(build): every running build knows its repository — die laufenden Builds und das Worktree-Aufräumen unterscheiden Repositories
Der letzte Übertrag aus Sitzung C: `RunningBuild` trug kein Repository, der Executor ist aber instanzweit. Zwei Folgen, beide gemessen und jetzt behoben: - Das Worktree-Aufräumen schützte die Worktrees ALLER Repositories. Ein laufender Build eines anderen Repositories auf einem gleichnamigen Branch hielt hier einen Worktree fest, dessen Branch längst von origin weg war. - Die Current-Builds-Ansicht und `/api/builds/current` zeigten die Builds aller Repositories, schlugen ihren Status aber in den Ergebnissen NUR dieses Repositories nach — ein fremder Build fiel auf RUNNING zurück und zeigte einen Zustand, den niemand aufgezeichnet hat. Dasselbe beim Live-Log: der Schlüssel eines fremden Builds wurde beantwortet. `RunningBuild.repo` ist der `RepoContext` selbst, verglichen wird per Referenz — der Kontext ist die Identität (ADR 0009), und der Executor hat ihn in `startBuild` ohnehin zur Hand. Watcher, API und UI filtern damit auf ihr eigenes Repository. Drei neue Tests, Gegenprobe per Mutation gezogen: Nimmt man die drei Filter wieder heraus, fallen genau diese drei und sonst keiner. 493 Tests grün, ktlint sauber. Architektur-Skill und Plan (docs/plan/22-multi-repo.md) nachgezogen — die Aussage „RunningBuild carries no repository" stimmte nicht mehr. Was von Sitzung D bleibt: die repo-bezogenen Routen und die Oberfläche, die weiterhin nur `registry.current()` bedienen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6fba43c627
commit
6ca67a6a37
@@ -97,6 +97,7 @@ class BuildExecutor(
|
||||
val stagingDir = Files.createTempDirectory("werkator-build-")
|
||||
val runningBuild =
|
||||
RunningBuild(
|
||||
repo = repo,
|
||||
branch = branch,
|
||||
build = build,
|
||||
commit = commit,
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import de.hoennig.werkator.config.BuildDefinition
|
||||
import de.hoennig.werkator.repo.RepoContext
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
|
||||
/** Handle to a build accepted by the [BuildExecutor]; log paths become valid once the build runs. */
|
||||
data class RunningBuild(
|
||||
/**
|
||||
* The repository this build belongs to; the context object is the identity
|
||||
* (ADR 0009), so it compares by reference. Without it neither the current-builds
|
||||
* view nor the watcher's worktree pruning could tell two repositories apart —
|
||||
* both would see every repository's running builds as their own.
|
||||
*/
|
||||
val repo: RepoContext,
|
||||
/** The git branch being built. */
|
||||
val branch: String,
|
||||
/** The build definition (job) this build runs; its settings are resolved from config at run time. */
|
||||
|
||||
@@ -55,11 +55,17 @@ class BuildsApiController(
|
||||
|
||||
private fun BuildResult.isLatestGreen(): Boolean = repository.latestGreenFor(name)?.artifactKey == artifactKey
|
||||
|
||||
/** The currently executing builds — several are possible, up to `executor.maxConcurrent`. */
|
||||
/**
|
||||
* The currently executing builds of the served repository — several are possible,
|
||||
* up to `executor.maxConcurrent`. The executor is instance-global and returns the
|
||||
* builds of every registered repository, so this view filters: its [repository]
|
||||
* holds only this repository's results, and a foreign build looked up in them
|
||||
* would fall back to RUNNING and show a status nobody recorded.
|
||||
*/
|
||||
@GetMapping("/api/builds/current")
|
||||
fun current(): List<CurrentBuildDto> {
|
||||
val results = repository.history()
|
||||
return buildExecutor.currentBuilds().map { build ->
|
||||
return buildExecutor.currentBuilds().filter { it.repo === repo }.map { build ->
|
||||
CurrentBuildDto(
|
||||
branch = build.branch,
|
||||
name = build.name,
|
||||
@@ -82,7 +88,7 @@ class BuildsApiController(
|
||||
@RequestParam(defaultValue = "0") offset: Long,
|
||||
): ResponseEntity<Any> {
|
||||
val build =
|
||||
buildExecutor.currentBuilds().firstOrNull { it.artifactKey == artifactKey }
|
||||
buildExecutor.currentBuilds().firstOrNull { it.repo === repo && it.artifactKey == artifactKey }
|
||||
?: return notFound("no running build with artifact key '$artifactKey'")
|
||||
return ResponseEntity.ok(readLogTail(artifactKey, build.liveLogFile, offset))
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class UiController(
|
||||
val links = baseModel(model, view = "current", pageTitle = "Current Builds")
|
||||
val results = repository.history()
|
||||
val currentBuilds =
|
||||
buildExecutor.currentBuilds().map { build ->
|
||||
buildExecutor.currentBuilds().filter { it.repo === repo }.map { build ->
|
||||
CurrentBuildView(
|
||||
branch = build.branch,
|
||||
name = build.name,
|
||||
|
||||
@@ -506,7 +506,10 @@ class Watcher(
|
||||
}
|
||||
val keep = originBranches.map { ArtifactKeys.branchKey(it) }.toMutableSet()
|
||||
// never delete under a build that is still queued or executing
|
||||
buildExecutor.currentBuilds().forEach { keep += ArtifactKeys.branchKey(it.branch) }
|
||||
buildExecutor
|
||||
.currentBuilds()
|
||||
.filter { it.repo === repo }
|
||||
.forEach { keep += ArtifactKeys.branchKey(it.branch) }
|
||||
repo.results
|
||||
.latestPerName()
|
||||
.filter { it.status == BuildStatus.PENDING || it.status == BuildStatus.RUNNING }
|
||||
|
||||
Reference in New Issue
Block a user