made the pull-request gate configurable: added watcher.pullRequestGate (default true); set false for plain-git origins without pull-request refs, so requirePullRequest gates are ignored and gated branches build on new commits; added the PR-doc

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Michael Hoennig
2026-07-08 15:50:45 +02:00
co-authored by Claude Fable 5
parent 83e70e73c3
commit fd326f03fc
7 changed files with 184 additions and 3 deletions
@@ -152,6 +152,9 @@ class InitCommand(
pollInterval: 10s
# max commit age for new origin branches to be pulled automatically
newBranchMaxAge: 5d
# honor branches.<name>.requirePullRequest; set false for a plain git origin
# without pull-request refs (refs/pull/*/head) — gated branches then build on new commits
pullRequestGate: true
# Per-branch build configuration.
# Use "default" as the fallback for all branches not listed explicitly.
@@ -49,6 +49,13 @@ data class WatcherConfig(
/** Delay between poll cycles, e.g. `10s` or `1m`. */
val pollInterval: String = "10s",
val newBranchMaxAge: String = "5d",
/**
* Honor the `branches.<name>.requirePullRequest` gates. Set false for a plain git
* origin without pull-request refs (no Gitea/GitHub) — gated branches then build
* on new commits like any other branch. Typically overridden per machine in
* `.git/gittally/.gittally.yml` when the committed config enables the gates.
*/
val pullRequestGate: Boolean = true,
)
data class BranchConfig(
@@ -175,7 +175,9 @@ class Watcher(
* repository, not by resetting the local ref like legacy. A new commit for a
* branch that is still pending/running waits for a later cycle (queue-behind).
* With `requirePullRequest`, the branch head must match a pull-request head on
* origin (`refs/pull/<n>/head`); manual `build` commands bypass this gate.
* origin (`refs/pull/<n>/head`); manual `build` commands bypass this gate, and
* `watcher.pullRequestGate: false` disables it globally for plain-git origins
* without pull-request refs.
*/
private fun startBuildIfDue(
branch: String,
@@ -192,7 +194,10 @@ class Watcher(
if (!allowSameCommit && latest?.commit == commit) {
return false
}
if (branchConfig(config, branch).requirePullRequest && commit !in pullRequestHeads.value) {
if (config.watcher.pullRequestGate &&
branchConfig(config, branch).requirePullRequest &&
commit !in pullRequestHeads.value
) {
log.info("not enqueueing branch {}: no pull request has head commit {}", branch, commit)
return false
}
@@ -256,6 +256,24 @@ class WatcherTest : FunSpec() {
verify(exactly = 0) { harness.gitService.pullRequestHeads(any()) }
}
test("a disabled pull-request gate builds gated branches on plain-git origins without querying pull-request refs") {
val harness =
Harness(
GitTallyConfig(
watcher = WatcherConfig(pullRequestGate = false),
branches = mapOf("default" to BranchConfig(requirePullRequest = true)),
),
)
every { harness.gitService.originBranches(any()) } returns listOf("feature/no-pr")
every { harness.gitService.newOriginBranches(any(), any()) } returns listOf("feature/no-pr")
every { harness.gitService.originHeadCommit("feature/no-pr", any()) } returns "commit-solo"
harness.watcher.poll(harness.workingDir)
harness.startedBuilds shouldContainExactly listOf("feature/no-pr" to "commit-solo")
verify(exactly = 0) { harness.gitService.pullRequestHeads(any()) }
}
test("a branch entry overrides requirePullRequest from the default entry") {
val harness =
Harness(