The branch config takes precedence, including its build definitions
A branch's committed .gittally.yml describes that branch's CI, so it wins over the project and repo-install config — the `builds` section included. Pinning it was wrong: a new build definition can only be tried out by committing it on a branch, and pinned it neither took effect at build time nor existed for the watcher, so the job silently never ran. The watcher now decides per branch from that branch's own definitions, reading its committed config via `git show` and caching it by head commit, so the read happens only when the branch moved; an unreadable config falls back to the primary definitions instead of failing the poll cycle. A branch's definitions are evaluated for that branch alone, so a definition committed on one branch can never trigger builds of another. The pinned set is reduced to what does not describe this branch's build: secrets (`git`), the host and repository sections (`server`, `gitea`, `executor`, `watcher`), the sandbox policy (`docker.enabled`/`network`), and the trust gate (`requirePullRequest`). Letting a branch set its own build command through a definition grants no new power — `branches.*. buildCommand` always allowed exactly that — while the sandbox and the gate decide whether untrusted branch code runs on the host at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a3faa172f8
commit
f5871a0442
@@ -117,3 +117,9 @@ Top-level `builds` with `onPush`/`atTimes`, as specified above.
|
||||
|
||||
Follow-up (2026-08-28): mixing the execution key `maxConcurrent` into the `builds` section as a reserved key proved confusing — it is not a build definition.
|
||||
The concurrency limit moved to `executor.maxConcurrent` (a new section for execution settings), without a compatibility alias, so the `builds` section holds build definitions only.
|
||||
|
||||
Follow-up (2026-08-29): pinning the whole `builds` section against the branch layer was wrong and is reverted.
|
||||
A branch's committed `.gittally.yml` describes that branch's CI, and a new `builds` configuration can only be tried out by committing it on a branch — pinned, it was neither effective at build time nor visible to the watcher, so the job silently did not exist.
|
||||
The branch layer now carries `builds` too: the watcher reads each origin branch's committed config (`git show`, cached by head commit) to decide which of *that branch's* builds are due, and a branch's definitions are evaluated for that branch alone, so they can never trigger builds of another branch.
|
||||
The pinned set is reduced to what does not describe this branch's build: secrets (`git`), the host/repository sections (`server`, `gitea`, `executor`, `watcher`), the sandbox policy (`docker.enabled`/`docker.network`), and the trust gate (`requirePullRequest`).
|
||||
Letting a branch set its own `buildCommand` through a definition grants no new power — `branches.*.buildCommand` always allowed exactly that — whereas the sandbox and the gate decide whether untrusted branch code runs on the host at all, and therefore stay server-side.
|
||||
|
||||
+32
-18
@@ -8,29 +8,42 @@ GitTally is configured via YAML files. Settings are merged from several sources
|
||||
|--------------------------|----------------------------|------------------|----------------------------------------------|
|
||||
| Project config | `.gittally.yml` | Yes | Shared team settings |
|
||||
| Repo installation config | `.git/gittally/.gittally.yml` | No | Machine- or user-specific overrides, secrets |
|
||||
| Build worktree config | `.gittally.yml` of the built commit | Yes | Per-branch build settings (build layer only) |
|
||||
| Branch config | `.gittally.yml` committed on a branch | Yes | That branch's build settings and build definitions |
|
||||
|
||||
The repo install config (`.git/gittally/.gittally.yml`) wins on any key present in both files. Typically used to set `git.token` and `git.account` without committing them.
|
||||
|
||||
### Per-branch build settings from the worktree
|
||||
### The branch layer: a branch describes its own CI
|
||||
|
||||
When a branch builds, its build config is resolved with an extra layer: the `.gittally.yml`
|
||||
committed on the branch being built (read from its build worktree) overrides the two layers
|
||||
above, giving the precedence **worktree > repo install > project**. So a branch can change its
|
||||
own `buildCommand`, `cleanCommand`, `artifactDirs`, log file names, and
|
||||
`docker.image`/`dockerfile`/`context`/`env`.
|
||||
The `.gittally.yml` committed on a branch is applied as a third layer on top of the two
|
||||
above, giving the precedence **branch > repo install > project**. It takes precedence for
|
||||
everything that describes how this branch is built: `buildCommand`, `cleanCommand`,
|
||||
`artifactDirs`, log file names, `docker.image`/`dockerfile`/`context`/`env`, and the whole
|
||||
`builds` section — its own definitions and its overrides of the definitions from the
|
||||
project config. That is how a new configuration is tried out: change it on a branch, and
|
||||
no other branch's builds are affected.
|
||||
|
||||
This layer applies **only** to the build itself. A pinned set is always taken from the repo
|
||||
install/project config and can never be set from the worktree:
|
||||
The branch layer is used in both places where it matters: the watcher reads the committed
|
||||
config of each origin branch (via `git show`, only when the branch moved) to decide which
|
||||
of *its* builds are due, and the build itself resolves its settings from the worktree of
|
||||
the commit being built.
|
||||
|
||||
- secrets and server-side settings: the whole `git`, `gitea`, and `server` sections;
|
||||
- the whole `builds` (build definitions) and `executor` sections;
|
||||
- the container sandbox policy: `docker.enabled` and `docker.network`.
|
||||
A branch's definitions apply to that branch alone. Their selectors are evaluated for it
|
||||
only, so a definition committed on one branch can never trigger builds of another — even
|
||||
when its `branches` selector names one.
|
||||
|
||||
This keeps a branch from disabling its own build container, changing its network mode, or
|
||||
reaching credentials. Jobs and their triggers are server-side decisions made before a
|
||||
build worktree exists, so the deprecated `autoBuild` schedules are read from the repo
|
||||
install/project config as well.
|
||||
A pinned set is always taken from the repo install/project config, because none of it
|
||||
describes this branch's build:
|
||||
|
||||
- secrets: the whole `git` section;
|
||||
- host- and repository-side settings: the whole `server`, `gitea`, `executor`, and `watcher` sections;
|
||||
- the container sandbox policy: `docker.enabled` and `docker.network`;
|
||||
- the trust gate: `requirePullRequest`.
|
||||
|
||||
This keeps a branch from reaching credentials, reporting statuses to another repository,
|
||||
raising the global concurrency, disabling its own build container, changing its network
|
||||
mode, or bypassing its own pull-request gate. Everything else is the branch's to decide —
|
||||
it can already run any command through `buildCommand`.
|
||||
The deprecated `autoBuild` schedules are read from the repo install/project config only.
|
||||
|
||||
## Inspect the Effective Config
|
||||
|
||||
@@ -260,9 +273,10 @@ Both parts combine as an intersection.
|
||||
The `branches.<name>.requirePullRequest` gate stays a branch property and gates all watcher-triggered builds of that branch.
|
||||
|
||||
Overrides: `buildCommand`, `cleanCommand`, `artifactDirs`, `stdoutLog`/`stderrLog`, and the docker image keys (`image`, `dockerfile`, `context`, `env`).
|
||||
The effective settings of one build on one branch merge in this order: defaults → `branches.default` → `branches.<branch>` → the worktree's committed `.gittally.yml` → the build definition's overrides.
|
||||
A definition has no `docker.enabled`/`docker.network` and no `requirePullRequest` — those are pinned branch properties, see [the branch layer](#the-branch-layer-a-branch-describes-its-own-ci).
|
||||
The effective settings of one build on one branch merge in this order: defaults → `branches.default` → `branches.<branch>` → the branch's committed `.gittally.yml` → the build definition's overrides.
|
||||
Unset keys fall back; the definition wins last because it is the job.
|
||||
The `builds` and `executor` sections are pinned: they always come from the repo install/project config, and the `.gittally.yml` committed on a branch can neither define jobs nor change the concurrency.
|
||||
Definitions themselves are part of the branch layer: a branch may add its own and override those from the project config, for its own builds only.
|
||||
|
||||
The implicit `default` build (`onPush: true`, all branches) preserves the behavior without any definitions; defining other builds does not disable it, `builds.default.onPush: false` does.
|
||||
The `default` build records under the plain branch name; every other build records under `<branch>@<name>` with its own row in the branches view (sorted after its branch), its own `retentionPerBranch` count, latest status, and permanent latest-green artifact link.
|
||||
|
||||
Reference in New Issue
Block a user