How I rebased changes on top of other rebased changes in Git
A while ago I wrote an entry on some git repository changes that I didn't know how to do well. One of them was rebasing my own changes on top of a repository that itself had been rebased; in the comments, Aristotle Pagaltzis confirmed that his Stackoverflow answer about this was exactly what I wanted. Since I've now actually gone through this process for the first time, I want to write down the details for myself, with commentary to explain how and why everything works. Much of this commentary will seem obvious to people who use Git a lot, but it reflects some concerns and confusions that I had at the time.
First, the repositories involved. rc is the master upstream repository for Byron Rakitzis's Unix reimplementation of Tom Duff's rc shell. It is not rebased; infrequent changes flow forward as normal for a public Git repo. What I'm going to call muennich-rc is Bert Münnich's collection of interesting modifications on top of rc; it is periodically rebased, either in response to changes in rc or just as Bert Münnich does development on it. Finally I have my own repository with my own local changes on top of muennich-rc. When muennich-rc rebases, I want to rebase my own changes on top of that rebase.
I start in my own repository, before fetching anything from upstream:
git branch old-me
This creates a branch that captures the initial state of my tree. It's not used in the rebasing process; instead it's a safety measure so that I can reset back to it if necessary without having to consult something like the git reflog. Because I've run
git branchwithout an additional argument,
old-meis equivalent to
masteruntil I do something to change
git branch old-muennich muennich/master.
old-muennichhave been created as plain ordinary git branches, not upstream tracking branches, their position won't change regardless of fetching and other changes during the rebase. I'm really using them as bookmarks for specific commits instead of actual branches that I will add commits on top of.
(I'm sure this is second nature to experienced Git people, but when I made
old-muennichI had to pause and convince myself that what commit it referred to wasn't going to change later, the way that
masterchanges when you do a '
git pull'. Yes, I know, '
git pull' does more than '
git fetch' does and the difference is important here.)
This pulls in the upstream changes from muennich-rc, updating what
muennich/masterrefers to to be the current top commit of muennich-rc. It's now possible to do things like '
git diff old-muennich muennich/master' to see any differences between the old muennich-rc and the newly updated version.
(Because I did
git fetchinstead of
git pullor anything else, only
muennich/masterchanged. In particular,
masterhas not changed and is still the same as
git rebase --onto muennich/master old-muennich master
This does all the work (well, I had to resolve and merge some conflicts). What it means is 'take all of the commits that go from
masterand rebase them on top of
muennich/master; afterward, set the end result to be
(If I omitted the
old-muennichargument, I would be trying to rebase both my local changes and the old upstream changes from muennich-rc on top of the current muennich-rc. Depending on the exact changes involved in muennich-rc's rebasing, this could have various conflicts and bad effects (for instance, reintroducing changes that Bert Münnich had decided to discard). There is a common ancestor in the master rc repository, but there could be a lot of changes between there and here.)
The local changes that I added to the old version of muennich-rc are exactly the commits from
master(ie, they're what would be shown by '
git log old-muennich..master', per the git-rebase manpage), so I'm putting my local commits on top of
muennich/master. Since the current
muennich/masteris the top of the just-fetched new version of muennich-rc, I'm putting my local commits on top of the latest upstream rebase. This is exactly what I want to do; I'm rebasing my commits on top of an upstream rebase.
- After the dust has settled, I can get rid of the two branches I was
using as bookmarks:
git branch -D old-me git branch -D old-muennich
I have to use
-Dbecause as far as git is concerned these branches both have unmerged changes. They're unmerged because these branches have both been orphaned by the combination of the muennich-rc rebase and my rebase.
Because I don't care (much) about the old version of my changes that are on top of the old version of muennich-rc, doing a rebase instead of a cherry-pick is the correct option. Following my realization on cherry-picking versus rebasing, there are related scenarios where I might want to cherry-pick instead, for example if I wasn't certain that I liked some of the changes in the rebased muennich-rc and I might want to fall back to the old version. Of course in this situation I could get the same effect by keeping the two branches after the rebase instead of deleting them.