Repository navigation
chore: detect vnext release merge by ancestry, not branch name - #714
John McCall (lowlydba) wants to merge 3 commits into
Conversation
vnext-compat.yaml and rebase-vnext.yaml both special-cased head_ref == 'vnext' to skip their normal checks for the vnext release merge. That missed #709, which stages the release on a 2_0_0 branch built on top of vnext's tip instead of merging vnext directly. Detect the release merge by ancestry instead: origin/vnext being an ancestor of the PR head (vnext-compat) or the new main HEAD (rebase-vnext) means the branch already carries everything on vnext, so any staging branch is recognized the same way a PR literally named vnext was. Document the pattern in CONTRIBUTING.md. Fixes #713 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
🗺️ Schema reference docs preview is live!
Note ♻️ This preview updates automatically with each push to this PR. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
There was a problem hiding this comment.
🟡 Changes recommended
vnext-compat.yaml’s detect-release job can be skipped on non-main PRs while still being a required dependency, which can prevent the intended noop path from running.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Updates the repo’s “vnext release merge” detection logic in GitHub Actions to rely on git ancestry (rather than PR head branch name), so release merges staged on a vnext-based branch (e.g. 2_0_0) are treated the same as merges from vnext itself.
Changes:
- Add ancestry-based “release merge” detection to
vnext-compat.yamlvia a newdetect-releasejob and routecompat-check/noopoff its output. - Switch
rebase-vnext.yamlrelease-merge detection to usegit merge-base --is-ancestor origin/vnext <main>after an earlier checkout. - Document the supported staging-branch release flow in
CONTRIBUTING.md.
File summaries
| File | Description |
|---|---|
| CONTRIBUTING.md | Documents staging a release merge on a vnext-based branch and notes ancestry-based detection. |
| .github/workflows/vnext-compat.yaml | Adds an up-front detection job and uses its output to skip compat checks for release merges. |
| .github/workflows/rebase-vnext.yaml | Moves checkout earlier and detects release merges via ancestry instead of PR head branch name. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
There was a problem hiding this comment.
🔵 Needs a closer look
It changes release-critical CI workflow control flow and the author notes it hasn’t been exercised against real PR/workflow runs yet.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
CONTRIBUTING.md:119
- In the inline shell command, using
<head>can be misleading because copying it into a shell treats<head>as input redirection, not a placeholder argument. Consider using a placeholder likeHEAD_SHA(or$HEAD_SHA) and briefly clarifying what it refers to (PR head SHA vs newmainSHA) so the example is copy/paste-safe.
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Lite
Victor Schappert (vcschapp)
left a comment
There was a problem hiding this comment.
Claude found a few issues. It took me a while to understand them and recast them in words that are more human-oriented and understandable, but here they are!
| if git merge-base --is-ancestor origin/vnext "$MAIN_SHA"; then | ||
| echo "::notice::Skipping rebase: origin/vnext is already an ancestor of main, this is a release merge." |
There was a problem hiding this comment.
I suggest we drop the skip step because if vnext is an ancestor of main, the rebase should always be a clean fast-forward. Skipping doesn't really buy us anything and adds complexity.
I think it's probably wrong also, in that after this change, the rebase won't run on every merge to main, it'll only run when vnext contains at least one commit that's not on main. This will mean that if a sequence of PRs target main then main will drift further and further ahead of vnext.
Basically on the old system if you pushed a commit B on main after a release (r), vnext would be rebased up to A:
Starting point after release `r` (old system):
A ─── r main
\
v vnext (behind r)
Push a new commit `B` to `main` and `vnext` is fast-forwarded up to `B`:
A ─── r ─── B main
╱
v ──────╯ vnext
In the new system if this happens, then vnext is an ancestor of B so there's no fast-forward...
A ─── r ─── B main
\
v vnext (behind r)
And this would continue if you keep tacking on new commits C, D, etc.
A ─── r ─── B ─── C ─── D main
\
v vnext (drifting further behind, one push at a time...)
If we just get rid of the skip and always run the fast-forward I think we get rid of this problem, yes?
(Note: This is a Claude-detected bug but it makes sense to me when I play it through.)
| - run: echo "::notice::Base isn't main, or this is the vnext release merge — nothing to check here." | ||
|
|
||
| vnext-status: | ||
| name: vnext compatibility status |
There was a problem hiding this comment.
I am informed by my LLM overlords that this will fail open if detect-release fails resulting in an otherwise non-mergeable change being allowed to merge.
The mechanism is apparently this dependency graph
vnext-status ──▶ are-we-good ──▶ compat-check ──▶ detect-release
│ ▲
└────────▶ noop ──────────────────┘
The claim is that GitHub action dependency propagation causes compat-check to be skipped if detect-release fails; and that are-we-good treats a skip (compat-check in this case) as if it was a pass.
Can you double-check this?
| fi | ||
|
|
||
| git fetch origin vnext | ||
| if git merge-base --is-ancestor origin/vnext "$PR_HEAD_SHA"; then |
There was a problem hiding this comment.
[2/2] LLM overlord is reporting that origin/vnext may not be populated after git fetch origin vnext due to Git quirks. Apparently an alternative that is certain to work/can't break unexpectedly is to refer to FETCH_HEAD instead of origin/vnext.
|
|
||
| if [ "$HEAD_REF" = "vnext" ]; then | ||
| echo "Skipping: this is a vnext→main release merge." | ||
| if git merge-base --is-ancestor origin/vnext "$MAIN_SHA"; then |
There was a problem hiding this comment.
[1/2] LLM overlord is reporting that origin/vnext may not be populated after git fetch origin vnext due to Git quirks. Apparently an alternative that is certain to work/can't break unexpectedly is to refer to FETCH_HEAD instead of origin/vnext.
|
|
||
| git fetch origin vnext | ||
| if git merge-base --is-ancestor origin/vnext "$PR_HEAD_SHA"; then | ||
| echo "is_release=true" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
Same root cause as the comment on the skip issue, the --is-ancestor check will result in is_release=true during any period when vnext hasn't diverged from main, even though most of those occurrences wouldn't be considered releases.
My understanding of what we're trying to do here is that is_release is the workflow's way of answering the question, is this PR the one that ships accumulated breaking changes into the main line, rather than an ordinary change? And we want to know this because in the case where we were merging in a bunch of breaking changes from vnext, the compatibility check really would fail because it'd be replaying vnext's commits on top of itself.
Current check is saying "if vnext is an ancestor of main it's a release" which includes the case where main is just organically ahead of vnext because there are no new breaking changes, so it'll be true too often. If we exclude that case then I think it works:
git fetch --no-tags origin \
'+refs/heads/main:refs/remotes/origin/main' \ # This +refs business is meant to ensure
'+refs/heads/vnext:refs/remotes/origin/vnext' # you can validly refer to `origin/vnext and origin/main`
if git merge-base --is-ancestor origin/vnext "$PR_HEAD_SHA" \
&& ! git merge-base --is-ancestor origin/vnext origin/main; then
echo "is_release=true" >> "$GITHUB_OUTPUT"
else
echo "is_release=false" >> "$GITHUB_OUTPUT"
fi
Context
#709 discussed two ways to make its
2_0_0staging branch a supported release path instead of a one-off. This picks option 2: keep the staging branch and formalize the detection.Fixes #713
What changed
vnext-compat.yamlandrebase-vnext.yamlboth special-casedhead_ref == 'vnext'to skip their normal checks for the vnext release merge. That misses #709, whose head is2_0_0, a branch built on top ofvnext's tip rather thanvnextitself.Both workflows now detect the release merge by ancestry instead:
git merge-base --is-ancestor origin/vnext <head>. Ifvnextis already an ancestor of the PR head (vnext-compat) or the newmainHEAD (rebase-vnext), the branch already carries everything onvnext, so it's treated as the release merge regardless of its name.%%{init: {"theme": "dark", "flowchart": {"padding": 14}, "themeVariables": {"fontSize": "14px", "mainBkg": "#21262d", "nodeBorder": "#4493f8"}}}%% flowchart TD classDef default rx:8,ry:8,stroke-width:0.75px subgraph compat["vnext-compat.yaml (on PR)"] A["PR targets main"] --> B{"origin/vnext ancestor<br/>of PR head?"} B -- yes --> C["noop:<br/>release merge, skip"] B -- no --> D["compat-check:<br/>simulate squash + rebase vnext"] end subgraph rebase["rebase-vnext.yaml (on push to main)"] E["Push to main"] --> F{"origin/vnext ancestor<br/>of new main HEAD?"} F -- yes --> G["skip:<br/>release merge, no rebase"] F -- no --> H["rebase vnext onto main,<br/>force-push"] endvnext-compat.yamlneeded a newdetect-releasejob up front since a job'sif:can't run shell commands;compat-checkandnoopnow key off itsis_releaseoutput.rebase-vnext.yamljust moved its checkout earlier so the existing detection step can run the ancestry check locally instead of only inspecting the merged PR'shead.reffrom the GitHub API.CONTRIBUTING.mdnow documents the staging-branch pattern as a supported alternative to mergingvnextdirectly.Testing
No CI-only path here to exercise outside the real workflows: I validated both YAML files parse (
yaml.safe_load) and traced theif:/needs:wiring by hand, but haven't watched the ancestry check run against a real PR. Worth watchingvnext-statuson this PR itself once it opens againstmain, and watchingrebase-vnexton the next2_0_0-style release merge.