The package rename moved every file the older PR-docs link to, leaving 60 dead links. Only the link targets are rewritten, never the visible text and never a statement: those documents record what was true when they were written, GitTally in the prose included. A snapshot may be outdated; it should still be navigable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.4 KiB
Build Only Branches with a Pull Request
WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.
The Problem
The watcher builds every changed or recently created origin branch. On repositories with many work-in-progress branches this wastes build capacity on branches nobody asked to be verified. The desired policy is: only branches with a pull request get watcher builds. Detecting pull requests must not require a Gitea API token, so it must work for anonymous read access.
Non-Goals
- Distinguishing open from closed pull requests (requires the Gitea API, see Open Questions).
- Gating manual
gittally build <branch>invocations; an explicit command always builds. - Building the merge preview commit (
refs/pull/<n>/merge) instead of the branch head.
The Scenarios
Feature: watcher builds only pull-request branches
Background
- Gitea (like GitHub) exposes each pull request to plain git clients as a ref
refs/pull/<n>/headpointing at the PR's head commit. - A branch "has a pull request" when its origin head commit equals one of those pull-request head commits.
- The gate is configured per branch via
branches.<name>.requirePullRequest(defaultfalse); setting it underbranches.defaultapplies it to all branches.
Scenario#000.01: A gated branch with a pull request is built!
So that pull-request branches get their commit status verified as before.
- Given
requirePullRequest: trueapplies to branchfeature/pr- and
feature/prhas new commits on origin - and a ref
refs/pull/<n>/headon origin points at the head commit offeature/pr
- and
- When the watcher polls
- Then a build of
feature/prat that head commit is enqueued
Verified by
Scenario#000.02: A gated branch without a pull request is not built!
So that work-in-progress branches do not consume build capacity.
- Given
requirePullRequest: trueapplies to branchfeature/no-pr- and
feature/no-prhas new commits on origin - and no
refs/pull/<n>/headpoints at its head commit
- and
- When the watcher polls
- Then no build is enqueued
- and the skip is logged
Verified by
Scenario#000.03: Pull-request detection works without an API token!
So that GitTally needs no Gitea credentials for this feature.
- Given a git remote that serves
refs/pull/<n>/headrefs - When pull-request heads are queried
- Then they are read via
git ls-remote origin "refs/pull/*/head"using the existing git authentication (or none)
Verified by
Scenario#000.04: Ungated setups make no extra remote calls!
So that existing installations see no new network traffic.
- Given no due branch has
requirePullRequestset - When the watcher polls
- Then
refs/pull/*/headis not queried at all
Verified by
Scenario#000.05: A branch entry overrides the default!
So that permanent branches like main keep building after merges, whose merge commits never match a pull-request head.
- Given
branches.default.requirePullRequest: true- and
branches.main.requirePullRequest: false
- and
- When
mainhas new commits and the watcher polls - Then
mainis built without any pull-request check
Verified by
Scenario#000.06: Auto builds respect the gate!
So that scheduled rebuilds follow the same policy as push-triggered builds.
- Given a branch with
autoBuild.enabledandrequirePullRequest: true- and its head commit matches no pull-request head
- When an auto-build slot becomes due
- Then no build is enqueued
- and the slot stays untriggered, so it is retried on later poll cycles
Verified by
Scenario#000.07: The gate can be disabled globally for plain git origins!
So that the same committed configuration works on environments whose origin is plain git without pull-request refs (no Gitea/GitHub), e.g. a local test server.
- Given
requirePullRequest: trueapplies to branchfeature/no-pr- and
watcher.pullRequestGate: falseis set (typically in the machine-specific.git/gittally/.gittally.yml)
- and
- When the watcher polls and
feature/no-prhas new commits - Then a build is enqueued like for an ungated branch
- and
refs/pull/*/headis not queried at all
- and
Verified by
The Solution
GitService.pullRequestHeads() lists refs/pull/*/head on origin via git ls-remote and returns the set of head commit SHAs.
The refs are read remotely instead of being fetched, because the default fetch refspec only covers refs/heads/* and mirroring pull refs locally would bloat every clone.
The watcher's startBuildIfDue gate compares the branch's origin head commit against that set when the resolved branch config has requirePullRequest.
The set is computed lazily and shared across one poll cycle, so ls-remote runs at most once per cycle and only when a gated branch is otherwise due.
The new config key lives in BranchConfig with default false, so the feature is opt-in and existing configurations behave unchanged.
watcher.pullRequestGate (default true) turns all requirePullRequest gates off globally, because a plain git origin serves no refs/pull/*/head and gated branches would otherwise never build there; being a property of the environment, it is typically set in the machine-specific .git/gittally/.gittally.yml.
Config reference, init templates, and CLAUDE.md were updated in sync, including the recommended setup (default: true, main: false).
Open Questions
- Closed pull requests whose head ref still equals the branch head also pass the gate, because matching is by commit id only; distinguishing open from closed would require the Gitea API. Currently implemented: any matching
refs/pull/<n>/headcounts. - A due auto-build slot of a gated branch without a pull request is re-checked every poll cycle for the rest of the day instead of being marked as skipped. Currently implemented: the slot stays untriggered, mirroring the behavior while a build is still running.
Follow-up PRs
- Optionally use the Gitea API (when a token is configured) to restrict the gate to open pull requests.