`builds` and the legacy `branches` are now either/or: `branches` is read
only while the merged configuration defines no build at all — a leftover
`builds.maxConcurrent` is not one — and ignored with a warning as soon as
one exists. Two half-answers to "what does this build run" would silently
pull against each other, and the committed configs still carrying both
must not change behaviour before they are migrated.
A definition therefore gained the settings it was missing:
`requirePullRequest` and `docker.enabled`/`network`. Those stay pinned —
`stripPinned` now removes them from a branch layer wherever they appear,
in a definition as well as in a legacy branch entry.
`builds.default` becomes the base every other definition inherits its
settings from, never its trigger: `onPush`, `atTimes`, `branches`, and
`activeWithin` say when and where *this* build runs. The inheritance is
applied after all layers are merged, which is what makes a build invented
on a branch inherit the host's sandbox policy instead of the data-class
default — otherwise a branch could get a native build past the pinning by
defining a job the host has never heard of.
Two bugs found on the way, both the same shape as the build command the
artifact page used to get wrong:
- `FileArtifactStore` read the artifact directories from the plain branch
settings, so a job adding its own `artifactDirs` never had them stored.
It goes through `GitTallyConfig.buildSettings` now, like everything else
that asks what a build runs.
- `Watcher.definitionsFor` cached the per-branch definitions by head
commit alone, so an edited machine or project config only took effect
once the branch moved — on a quiet branch, never. The primary config is
part of the cache key now.
Renaming gitea.statusContext while a build runs splits that build over two
contexts: the abandoned one keeps its 'build running' entry, and Gitea reports
the commit as pending forever. Gitea cannot delete a commit status, so document
the manual closing POST as the only way out.
Co-Authored-By: Claude <noreply@anthropic.com>
./gradlew runtimeBundle packs a jlink-trimmed JRE, gittally.jar, and a
launcher script into one tarball, unpacked to ~/opt/gittally on the target
host; init --systemd works from the bundle unchanged because java.home and
the running-jar path resolve into it. Chosen over a GraalVM native image
(Spring AOT evaluates bean conditions at build time, which cannot represent
the dual-context CLI/server wiring) and over a containerized runtime — see
ADR 0006 and docs/plan/15-runtime-bundle-distribution.md, which also records
the full vm2176-to-vm4006 migration walkthrough.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>