Skip to content

Backport every commit of a rebase-merged pull request - #80

Draft
aiolibsbot wants to merge 1 commit into
sanitizers:masterfrom
aiolibsbot:koan/rebase-merge-commit-series
Draft

aiolibsbot wants to merge 1 commit into
sanitizers:masterfrom
aiolibsbot:koan/rebase-merge-commit-series

Conversation

@aiolibsbot

@aiolibsbot aiolibsbot commented Sep 17, 2026 •

Copy link
Copy Markdown

What

Cherry-pick all the commits a pull request contributed, not just the one GitHub reports as merge_commit_sha.

Why

merge_commit_sha means different things per merge strategy. A squash-"merge" holds the whole PR in it; a rebase-"merge" (and a fast-forward push) replays the PR's commits onto the base branch and points merge_commit_sha at the last one.

Patchback picked that single commit either way. A 3-commit rebase-merged PR was backported as its final commit only — and the check run went green. Silent partial backport reported as success. tests/test_pick_target.py::test_rebase_merge_tip_alone_would_lose_commits pins that premise: the old pick yields 1 of 3 files.

Resolves #35.

How

Count, don't classify. git cherry <merge commit> <PR head> marks each PR commit -/+ by patch identity, which a rebase preserves:

  • all upstream → replayed series → pick the sha~N..sha range
  • none upstream → single squashed commit → pick it alone (unchanged)
  • anything between → a shape we can't name → refuse, don't backport a slice

Fast-forward pushes leave no rewritten copies for git cherry to match, so they're spotted by the PR head resolving to merge_commit_sha; the payload's commits count says how far back the series goes. A ~N range spanning a different number of commits than the PR has (merge commits in the branch) is refused too.

Decisions worth flagging:

  • The PR head comes from refs/pull/*/head — works for forks. If that fetch fails, the historical single-commit pick is kept rather than failing closed: it's correct for squash-"merges", the default strategy. Loud logger.error either way.
  • The refusal path omits the manual-backport guide, whose git cherry-pick <merge commit> step is the exact operation that would drop the rest of the series. On the conflict path the guide is still shown, so the error now carries an explicit note naming the range to pick instead.
  • Decision logic lives in patchback/pick_target.py — pure, stdlib-only, no I/O.

Testing

19 tests, pytest + git only. Beyond the pure unit tests, each merge strategy is built in a real repo (squash / rebase / fast-forward / merge-commits-in-branch) and the resolved target is actually cherry-picked, asserting the backported tree contains every file the PR added.

Not done: exact handling of a fast-forwarded branch that contains merge commits — refused instead. Revisit if anyone reports it.

pytest.ini and .github/workflows/tests.yml are byte-identical to the copies on #72/#73/#77/#79, so add/add merges cleanly whatever the order. Overlaps #79 in the cherry-pick call site.


Quality Report

Changes: 5 files changed, 429 insertions(+), 5 deletions(-)

Code scan: clean

Tests: failed (command not found)

Branch hygiene: clean

Generated by Kōan

GitHub reports a `merge_commit_sha` for every merged pull request, but
what that commit holds depends on the merge strategy. A squash-"merge"
puts the whole pull request in it. A rebase-"merge" or a fast-forward
push replays the pull request's commits onto the base branch and points
`merge_commit_sha` at the *last* one.

Patchback cherry-picked that single commit either way, so a three-commit
rebase-merged pull request was backported as its final commit only — and
the check run was still marked green. A partial backport reported as a
success is worse than a failed one.

Ask Git which of the pull request's commits already landed instead of
guessing the strategy: `git cherry <merge commit> <pull request head>`
marks each one, since a rebase preserves patch identity. All of them
upstream means a replayed series, so pick the `sha~N..sha` range; none
means a single squashed commit, so keep picking it alone. A fast-forward
is spotted by the head resolving to the merge commit itself, where the
commit count from the webhook payload says how far back the series goes.

Anything in between those two answers is a shape this cannot name, as is
a series whose `~N` range spans a different number of commits than the
pull request has — a branchy history rather than the flat one a rebase
produces. Both refuse rather than backport a slice of the pull request.

The pull request head comes from the `refs/pull/*/head` ref, which also
covers pull requests from forks. If fetching it fails, the historical
single-commit pick is kept: that is correct for squash-"merges", the
default strategy and the common case.

Resolves sanitizers#35

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FR] Add support for backporting PRs “merged” using the rebase strategy

1 participant