Skip to main content

Re-parent a branch by merge instead of rebase: the git merge equivalent of `git rebase --onto`, without rewriting history.

Project description

git-merge-onto

Re-parent a branch by merge instead of rebase. It is the git merge equivalent of git rebase --onto: it moves your branch onto a new base and drops an old one, without rewriting history and without a force-push.

git merge-onto <new> <old>

Run on the branch you want to move (HEAD). It performs a 3-way merge of <new> into HEAD whose merge base is forced to merge-base(HEAD, <old>). The result keeps HEAD's own changes, drops the content it shared with its old parent <old>, and records <new> as a real ancestor. The new tip is a descendant of the old one, so the branch updates as a fast-forward push.

Why not just git merge?

A plain git merge <new> picks its base from the commit graph, and for a re-parent that base is wrong in one of two ways:

  • Too low. When <new> already contains <old>'s content but not its commit (the usual case after <old> was squash-merged into <new>), the natural base sits below <old>, so the merge re-applies <old> and conflicts on every hunk the squash reshaped. -X ours papers over this but also silently discards real changes that landed on <new>.
  • Too high. When the new parent transitively contains HEAD's own commit (a stack reorder), the natural base is HEAD itself, so the merge fast-forwards and silently drops your branch's change, leaving an empty diff.

Forcing the base to merge-base(HEAD, <old>) is correct in both. That base is the one thing git porcelain cannot express, which is the whole reason this tool exists.

Install

# one-off, no install
uvx git-merge-onto <new> <old>

# or install so `git merge-onto` works as a git subcommand
uv tool install git-merge-onto
git merge-onto <new> <old>

It is a small, dependency-free Python package. Requires Python 3.9+ and git 2.13+.

Example

You branched feature off develop, then branched followup off feature. feature gets squash-merged into develop. Now move followup onto develop, dropping feature, keeping followup's own commits and its review history:

git switch followup
git merge-onto develop feature
git push            # fast-forward, no --force
gh pr edit followup --base develop

A clean merge commits straight away. A conflict is left in your working tree like any merge: edit the files, git add -A, git commit.

Flags

flag meaning
-m, --message <msg> commit message for a clean merge
--absorbed record <old> as an extra parent (see below)
--quiet do not echo the executed git commands
--version print the version

--absorbed: mark the old parent as merged

By default the re-parent drops <old>: the result does not descend from its tip, so git and GitHub keep treating <old> as unmerged. That is right when you pull a branch out from under a live parent, and wrong when <new> already carries <old>'s changes without its commits -- after a squash merge, a rebase merge, or a cherry-pick. --absorbed asserts the latter: <old>'s tip is recorded as an extra parent of the merge, so the result descends from it and git and GitHub treat <old> as merged.

In the example above, with git merge-onto --absorbed develop feature the re-homed followup contains feature, so its PR stays mergeable (and CI keeps running) even while it is still based on feature, and GitHub shows feature as merged into it. If feature's tip is already an ancestor the extra parent is redundant and git drops it, so the flag is safe to pass whenever the premise holds.

This cannot be a default: whether <old>'s changes are already in <new> is a fact about how <old> landed, not something visible in the commit graph. When they are not (moving a branch off a live parent), the extra parent would falsely mark <old> as merged into your branch.

How it works

git merge-onto <new> <old> reduces to a single plumbing call:

git merge-recursive <merge-base(HEAD, old)> -- HEAD <new>

On a clean merge it writes the commit (parents [HEAD, new]) using the standard MERGE_HEAD machinery, so resolving a conflict is just git add then git commit, exactly like a normal merge. Nothing is rewritten and nothing is force-pushed.

Companion tools

git-merge-onto is the shared primitive behind two stacked-PR tools:

  • stack-mv rearranges open PR stacks before they land (reorder, move between stacks, insert a prerequisite).
  • autorestack-action re-homes descendant PRs after an ancestor lands.

Both re-parent by merge, never by rebase. This package isolates the one operation git itself cannot do.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

git_merge_onto-0.2.0.tar.gz (7.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

git_merge_onto-0.2.0-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

File details

Details for the file git_merge_onto-0.2.0.tar.gz.

File metadata

  • Download URL: git_merge_onto-0.2.0.tar.gz
  • Upload date:
  • Size: 7.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for git_merge_onto-0.2.0.tar.gz
Algorithm Hash digest
SHA256 bc6e2b723a3d21271248e021a6f6d379435d878f1ac21f2b64c22fe2fbe832f1
MD5 24dc80f1e6d61d0b5f789852c42326a7
BLAKE2b-256 83cbfee0255d92f90c81a36b651e6d478e8b2187aadd03036a1006a771b5e6d1

See more details on using hashes here.

File details

Details for the file git_merge_onto-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: git_merge_onto-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 9.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for git_merge_onto-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fbbccce13b93d499785defd1281d8f89624d8eb9e25a1f44fd9b242c29411926
MD5 630188bb0e1b71355d44420f4a224ddf
BLAKE2b-256 e9caa84602d6cc6a44a8c58feb5d4f7ceaf0ea385f4f07b98da6816f1e654d96

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page