Rebasing merged trees in Git
I have something akin to the following history (but more complicated with several merges):
F - G - H - K - L - … / / / I - J - M - … / / A - B - C - D - E - … A′- B′- C′- D′- E′
A and A′ are identical trees with different histories.
- Branch off a branch, How to rebase on another branch?
- using git rebase in the master branch
- Delete commits from a branch in Git
- Isn't git merge --squash really git rebase -squash?
- What does it mean to specify two blobs in `git rebase`?
- Easy way to Git squash?
I want to rebase all branch commits to equivalent commits on the prime branch, preserving merges (presumably by manually specifying the hashes of where the equivalent commits are on the rebased tree). How do I tell
K′ that it needs to merge from
J′? Or do I have to recreate these merge commits manually?
If I do
git rebase -p --onto B′ B L it fails to apply cleanly. I could rebase
D′, and then recreate merge
K myself, then rebase
K′ (and so on for other branches/merges not shown) but it would be a fair bit of work.
I have looked at several other questions but none of them featured merges.
2 Solutions collect form web for “Rebasing merged trees in Git”
Assuming this is either not a public repository, or any other users of the repository are ok with significant rewriting, the most efficient way to accomplish this is with
git filter-branch. One of the possible filters is
--parent-filter, which allows you to change the parentage of specific commits, somewhat similarly to grafting or rebasing a portion of the tree, but since you can also pass the
--all option, you can accomplish the effect on multiple branches in one invocation. This could also be accomplished with a
--commit-filter; but that’s a more general solution intended for changing other aspects of individual commits – not just the parents. You’d probably also want to use a
--tag-name-filter cat to move any tags in the portions of the tree being rewritten.
So the final command would look something like:
git filter-branch --parent-filter <somescript> --tag-name-filter cat -- --all
<somescript> is either properly quoted/escaped
bash code to replace
A' for the
B commit (details on exactly how the information is provided to the script and what the results of the script should be can be found in
git help filter-branch), or the name of an actual shell script that accomplishes the same.
There is also some cleanup to be done afterwards –
filter-branch leaves your original branches in place, but with new names (
refs/original/...) so you can recover if something doesn’t look right. There’s plenty of information out there on how to remove dead branches once you’re satisfied that
filter-branch did what you wanted, and re-packing your repository to recover the storage space, so I won’t replicate that here…
Have you tried using interactive rebasing? Basically, do
git rebase -i A and git will fire up your text editor with the commits after A listed. You can then move commits around and squash commits into each other to your heart’s content.