7.4 KiB
Build Definitions Replace Branch-Owned Schedules
Status:
- proposed: 2026-08-28
- accepted: -
- rejected: -
- superseded: -
Decision [proposed]: A top-level builds section defines named builds (jobs) with onPush/atTimes triggers and a branch selector — the branch-owned autoBuild schedule and the v0.9.13 per-slot buildCommand/name syntax are replaced by it.
branches stays what it is: per-branch build settings that every build inherits.
Context and Problem Statement
GitTally's configuration is branch-centric: branches.<name> holds the build settings, and the nightly schedule (autoBuild) hangs off the branch.
v0.9.13 added a per-slot buildCommand and name to autoBuild.times[], so a nightly slot could run a fuller check recorded in its own result pool.
That worked, but it is a job concept hidden inside a schedule entry: the slot carries a command, an identity, and (implicitly) a branch — everything a job has, in the wrong place.
Requirements that the branch-centric schema cannot express cleanly:
- Several builds of the same branch with different commands (quick check on push, full PIT test nightly).
- One scheduled build over a set of branches, e.g. "run
piTestFullnightly for every branch that had commits in the last 24 hours" — a schedule owned by a branch cannot select branches. - A name for such a build that is not welded to a single schedule slot.
Technical Background
Since v0.9.13 the result model already carries a pool identity per build (BuildResult.name), with name-keyed history, retention, branches-view rows, and permanent latest-green links, while everything git-side (origin lookups, pruning, worktrees, Gitea) stays keyed by the real branch.
So the persistence and UI side is prepared; what is missing is a first-class job in the configuration and the watcher.
Considered Options
- Keep extending the branch-owned
autoBuild(status quo of v0.9.13) - Top-level
buildssection with named build definitions (jobs) - Both concepts permanently in parallel
Keep Extending autoBuild
Every new need (per-slot command, per-slot name, branch selectors) grows another attribute inside autoBuild.times[].
Advantages
- No schema change, no migration.
Disadvantages
- The slot is a job in disguise; each extension makes the disguise worse.
- A schedule owned by one branch can never select other branches (
activeWithinis inexpressible). - Two
buildCommandkeys at different nesting levels with override semantics (the complaint that triggered v0.9.13's rework already).
Top-Level builds Section (chosen)
branches: # unchanged: per-branch build settings, worktree-overridable
default:
cleanCommand: rm -rf build
buildCommand: ./gradlew --console=plain --no-daemon prQuickCheck
builds:
# implicit when omitted or not overridden — exactly the pre-builds behavior:
# default:
# onPush: true
pitest:
atTimes: ["01:00"] # UTC HH:MM, plain strings only
activeWithin: 24h # only branches with commits in the last 24h
# branches: ["master"] # alternative/additional explicit selector
buildCommand: ./gradlew -PfullPitTest --console=plain --no-daemon piTestFull
A build definition has:
- Triggers:
onPush: true|false(defaultfalse; the implicitdefaultbuild hastrue) andatTimes: [HH:MM, …](default empty). A build may have both. - Branch selector:
branches: [names](default: all origin branches) andactiveWithin: <duration>(default: unset = no age filter; a branch qualifies while its origin head commit is younger than the duration). Both combine as an intersection. - Build-setting overrides:
buildCommand,cleanCommand,artifactDirs,stdoutLog/stderrLog, and the docker image keys (image,dockerfile,context,env) — anything unset falls back to the merged branch settings.
Semantics:
- Merge order for the effective settings of one build on one branch: config defaults →
branches.default→branches.<branch>→ the worktree's committed.gittally.yml(build keys, pinned keys stripped) →builds.<name>overrides. The build definition wins last because it is the job; it comes from the repo install/project config (server-side), never from the worktree. - Pool identity: the
defaultbuild records under the branch name (URLs, rows, retention as before); every other build records under<branch>@<build>(URL-sanitized, e.g./branches/master_pitest/…). Each pool keeps its own retention count, latest status, and permanent latest-green link. - Persistence: the result stores the build's name (
build, defaultdefault) next to the branch; the derived pool name keeps keying grouping and display. The v0.9.13buildCommandOverridefield is dropped: restart, retry, and startup recovery re-resolve the command from the current config by (branch, build) — a job definition in config is the source of truth, so a re-run of an old result uses the job's current command. - Triggers in the watcher:
onPushuses the existing change detection per pool ("already built" per pool and commit);atTimesfires once per day per slot per pool (state file keyed by pool, date, time). Thebranches.<name>.requirePullRequestgate stays a branch property and gates all watcher-triggered builds of that branch, as today. - Execution invariants unchanged: every build of a branch runs in that branch's worktree, at most one build per branch at a time,
builds.maxConcurrentacross branches, Gitea commit status per commit in the shared status context (last build of a commit wins).
Compatibility and migration:
- No
buildssection, or nodefaultentry: the implicitdefaultbuild (onPush: true, all branches) preserves today's behavior exactly. Defining other builds does not disable it;builds.default.onPush: falsedoes. branches.<name>.autoBuild(enabled+ plaintimes) keeps working for a transition period, internally mapped to a scheduled build of the branch's own pool, with a deprecation warning in the log;docs/configuration.mddocuments onlybuilds.- The v0.9.13 per-slot
buildCommandandnameare removed (not deprecated): released one day ago, configured nowhere.
Advantages
- Jobs are first-class: name, command, schedule, and branch selection in one place, no nesting tricks.
- Expresses the previously impossible selector cases (
activeWithin, explicit branch lists) naturally. - Reuses the v0.9.13 pool plumbing (result
name, retention, rows, permalinks) almost unchanged. - The trigger keys read as English:
onPush,atTimes.
Disadvantages
- The biggest config-schema change since the rewrite: watcher enqueue logic, config layering, docs,
inittemplates, and tests are all touched. - Two sections (
branches,builds) must be explained: settings per branch vs. jobs over branches. - Re-runs follow the current job definition instead of the recorded command — a deliberate semantic change against v0.9.13.
Both Concepts Permanently
Keep autoBuild (including the v0.9.13 slot syntax) forever next to builds.
Advantages
- Nothing ever breaks.
Disadvantages
- Two ways to express the same job, forever; every future feature must be specified against both.
Decision Outcome
Top-level builds with onPush/atTimes, as specified above.
branches.*.autoBuild survives one deprecation period as a mapped alias; the v0.9.13 slot extras are reverted.