Path filters computed a push's changed files as old..new, where old is the
branch's previous tip. That is only "what this push changed" when the push is a
fast-forward. A force-push rewrites the branch, and then old..new is the
difference between two histories — after a rebase, whatever the new base added
and nothing the branch itself touched.
So every filter concluded its job was unnecessary, and a rebased branch reported
build: success with test, sonar and vuln never run and never recorded as
skipped. On a repository that only accepts fast-forward merges, rebasing is the
normal path, not an edge case — this is how unverified code gets merged.
Observed landing this release: four branches, three rebases, sixteen jobs that should have run and did not.
The fix reuses the merge-base derivation #171 already added for a branch's first
push: if old is not an ancestor of sha, the branch was rewritten and the old
tip is not a meaningful base, so fall back to the merge base with the default
branch. That is the honest base either way — a filter is deciding about the
branch's relationship to its target, and the merge base is what expresses it. An
IsAncestor error falls open to the merge base too, keeping the existing rule
that a filter which cannot be evaluated must never silently skip CI.
TestQueueBranchBuildsRebaseFiltersAgainstMergeBase builds the trap: a branch
that changes app/a.go, rebased onto a main whose new commit is docs-only. It
asserts the fixture reproduces the trap (old..new contains the docs file and
not the code file) before asserting a build is queued, so it cannot pass
vacuously. It fails without the fix. TestQueueBranchBuildsOrdinaryPushStillFilters
continues to pass, so an ordinary docs-only push still filters.
Closes #176