Skip to content

chore: detect vnext release merge by ancestry, not branch name - #714

Closed
John McCall (lowlydba) wants to merge 3 commits into
mainfrom
lowlydba-release-2-0-0
Closed

John McCall (lowlydba) wants to merge 3 commits into
mainfrom
lowlydba-release-2-0-0

Conversation

@lowlydba

@lowlydba John McCall (lowlydba) commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Context

#709 discussed two ways to make its 2_0_0 staging 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.yaml and rebase-vnext.yaml both special-cased head_ref == 'vnext' to skip their normal checks for the vnext release merge. That misses #709, whose head is 2_0_0, a branch built on top of vnext's tip rather than vnext itself.

Both workflows now detect the release merge by ancestry instead: git merge-base --is-ancestor origin/vnext <head>. If vnext is already an ancestor of the PR head (vnext-compat) or the new main HEAD (rebase-vnext), the branch already carries everything on vnext, 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"]
    end
Loading

vnext-compat.yaml needed a new detect-release job up front since a job's if: can't run shell commands; compat-check and noop now key off its is_release output. rebase-vnext.yaml just moved its checkout earlier so the existing detection step can run the ancestry check locally instead of only inspecting the merged PR's head.ref from the GitHub API.

CONTRIBUTING.md now documents the staging-branch pattern as a supported alternative to merging vnext directly.

Testing

No CI-only path here to exercise outside the real workflows: I validated both YAML files parse (yaml.safe_load) and traced the if:/needs: wiring by hand, but haven't watched the ancestry check run against a real PR. Worth watching vnext-status on this PR itself once it opens against main, and watching rebase-vnext on the next 2_0_0-style release merge.

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>
@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

🗺️ Schema reference docs preview is live!

🌍 Preview https://staging.overturemaps.org/schema/pr/714/schema/index.html
🕐 Updated Sep 02, 2026 18:54 UTC
📝 Commit 6f32fdc
🔧 env SCHEMA_PREVIEW true

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>
@lowlydba
John McCall (lowlydba) marked this pull request as ready for review September 2, 2026 18:47
@lowlydba
John McCall (lowlydba) requested a review from a team as a code owner September 2, 2026 18:47
Copilot AI lite review requested due to automatic review settings September 2, 2026 18:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.yaml via a new detect-release job and route compat-check/noop off its output.
  • Switch rebase-vnext.yaml release-merge detection to use git 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.

Comment thread .github/workflows/vnext-compat.yaml
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Signed-off-by: John McCall <john@overturemaps.org>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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 like HEAD_SHA (or $HEAD_SHA) and briefly clarifying what it refers to (PR head SHA vs new main SHA) so the example is copy/paste-safe.
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

Comment on lines +73 to +74
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."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

This branch was successfully deployed

1 active deployment
staging — 6f32fdc4 Deployed Sep 2, 2026 by lowlydba via Deploy #490
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generalize vnext release-merge detection past head_ref == 'vnext'

4 participants