Show the command a build actually runs on its artifact page

The artifact page read `branches.<branch>.buildCommand` from the primary
config only, so for a named build on a branch with its own config it showed
a command that build never ran — on vm4006 it showed the host config's
command for a build that ran the branch definition's `pitestFull`.

Resolving "what does this build run" now has one implementation,
`GitTallyConfig.buildSettings(branch, build)`: the branch entry with the
build definition's overrides applied last. The executor uses it, and the
page resolves it against the branch layer committed at the build's own
commit — the same inputs the executor had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-29 07:52:22 +02:00
co-authored by Claude Opus 5
parent 939d8eeb9c
commit 1a84b1fc1b
6 changed files with 81 additions and 12 deletions
@@ -8,6 +8,7 @@ import de.hoennig.gittally.build.BuildResultRepository
import de.hoennig.gittally.build.BuildStatus
import de.hoennig.gittally.config.ConfigLoader
import de.hoennig.gittally.config.GitTallyConfig
import de.hoennig.gittally.git.GitService
import de.hoennig.gittally.metrics.SystemMetricsCollector
import io.kotest.core.spec.style.FunSpec
import io.mockk.clearMocks
@@ -52,6 +53,9 @@ class PermanentBranchRoutesTest : FunSpec() {
@MockkBean
lateinit var configLoader: ConfigLoader
@MockkBean
lateinit var gitService: GitService
@MockkBean
lateinit var metricsCollector: SystemMetricsCollector
@@ -81,11 +85,14 @@ class PermanentBranchRoutesTest : FunSpec() {
artifactStore,
controlTokens,
configLoader,
gitService,
metricsCollector,
branchListing,
branchPermalinks,
)
every { configLoader.load(any()) } returns GitTallyConfig()
every { configLoader.loadWithBranchLayer(any(), anyNullable()) } returns GitTallyConfig()
every { gitService.showFileAtCommit(any(), any(), any()) } returns null
every { controlTokens.token() } returns "test-token"
every { branchListing.branches(any()) } returns emptyList()
every { branchPermalinks.latestGreenBuild("main") } returns greenBuild
@@ -7,10 +7,13 @@ import de.hoennig.gittally.build.BuildResult
import de.hoennig.gittally.build.BuildResultRepository
import de.hoennig.gittally.build.BuildStatus
import de.hoennig.gittally.build.RunningBuild
import de.hoennig.gittally.config.BranchConfig
import de.hoennig.gittally.config.BuildDefinition
import de.hoennig.gittally.config.ConfigLoader
import de.hoennig.gittally.config.GitTallyConfig
import de.hoennig.gittally.config.GiteaConfig
import de.hoennig.gittally.config.ServerConfig
import de.hoennig.gittally.git.GitService
import de.hoennig.gittally.metrics.MetricAggregate
import de.hoennig.gittally.metrics.SystemMetrics
import de.hoennig.gittally.metrics.SystemMetricsCollector
@@ -58,6 +61,9 @@ class UiControllerTest : FunSpec() {
@MockkBean
lateinit var configLoader: ConfigLoader
@MockkBean
lateinit var gitService: GitService
@MockkBean
lateinit var metricsCollector: SystemMetricsCollector
@@ -103,6 +109,7 @@ class UiControllerTest : FunSpec() {
artifactStore,
controlTokens,
configLoader,
gitService,
metricsCollector,
branchListing,
branchPermalinks,
@@ -112,6 +119,8 @@ class UiControllerTest : FunSpec() {
server = ServerConfig(impressumUrl = "https://example.org/imprint"),
gitea = GiteaConfig(baseUrl = "https://git.example.org", owner = "acme", repo = "widget"),
)
every { configLoader.loadWithBranchLayer(any(), anyNullable()) } returns GitTallyConfig()
every { gitService.showFileAtCommit(any(), any(), any()) } returns null
every { controlTokens.token() } returns "test-token"
every { repository.latestGreenFor(any()) } returns null
}
@@ -264,6 +273,28 @@ class UiControllerTest : FunSpec() {
).andExpect(content().string(not(containsString("reports/tests/test/packages/index.html"))))
}
test("the artifact page shows the command of the build's own definition, not the plain branch command") {
val pitestResult =
successResult.copy(
build = "pitest",
name = "main@pitest",
artifactKey = "main-pitest-key",
)
every { repository.history() } returns listOf(pitestResult)
every { artifactStore.artifactDir("main-pitest-key") } returns null
every { configLoader.loadWithBranchLayer(any(), anyNullable()) } returns
GitTallyConfig(
branches = mapOf("default" to BranchConfig(buildCommand = "./gradlew quick-check")),
buildDefinitions = mapOf("pitest" to BuildDefinition(buildCommand = "./gradlew pitestFull")),
)
mockMvc
.perform(get("/builds/main-pitest-key"))
.andExpect(status().isOk)
.andExpect(content().string(containsString("./gradlew pitestFull")))
.andExpect(content().string(not(containsString("./gradlew quick-check"))))
}
test("artifact index links a single index-less report page as a directory, keeping the URL stable") {
val artifactDir = Files.createDirectories(tempDir.resolve("main-abc123-key"))
Files.createDirectories(artifactDir.resolve("reports/profile"))