Reminder about `git rebase onto`
Posted on
This is something that I haven’t had to do in a while, so I lost it as a part of my “git reflexes”. I had to do it today for the first time in over a year, so it kind of jogged my memory.
Using git rebase
Many folks are already familiar with basic git rebase
, but as a recap:
Let’s say that I’m working in a feature branch off of main
, but some other engineer gets their code merged to main
first (how dare they!). So, because I’m awesome, I want to make sure I test my code with the latest. I also like a tidy git history, so I don’t just merge main
into my feature branch. I do the following:
git checkout main
git pull
git checkout feat/JIRA-123
git rebase main
The above doesn’t mean I won’t have conflicts, but it does mean that any commits I have in feat/JIRA-123
get taken off the top of the commit log and then stacked on top of the commit log of main
.
Using git rebase --onto
But what if there is another level of indirection?
Let’s say I am working in a monorepo and I am working on an Epic to allow users to manage their filesystem. I might break that into two work items that can ship separately. For example maybe feat/JIRA-123
represents creation of a shared library for doing the low-level filesystem stuff and feat/JIRA-321
represents the work to add screens in the user interface that leverage the library. Even though I may have unit tests in feat/JIRA-123
to test the library, sometimes it is useful to work on both at the same time to have a faster feedback loop.
To do this, I have the following branches chained up:
main
-> feat/JIRA-123
-> feat/JIRA-321
As long as I’ve kept my commit history linear between the two feature branches, the commit history for feat/JIRA-123
might look like this:
917605a (HEAD -> feat/JIRA-123) chore: deprecate cross-compile for OS/360 and VAX 9000
3fa8d4c chore: makefile cross-compiles to OS/360 and VAX 9000
a8f5fef fix: library no longer deletes root filesystem
738bf4e feat: initial code for new library
0c1764f (origin/main, main) fix: temporary hack until new filesystem library is in place
This shows four commits in feat/JIRA-123
on top of main
. Pretty standard stuff.
The commit history for feat/JIRA-321
might look like this:
76951b6 (HEAD -> feat/JIRA-321) chore: update README
d4d2b9f feat: add new screen that leverages new filesystem library
917605a (feat/JIRA-123) chore: deprecate cross-compile for OS/360 and VAX 9000
3fa8d4c chore: makefile cross-compiles to OS/360 and VAX 9000
a8f5fef fix: library no longer deletes root filesystem
738bf4e feat: initial code for new library
0c1764f (origin/main, main) fix: temporary hack until new filesystem library is in place
This is telling us that we have two commits ontop of the feat/JIRA-123
branch, which in turn sits on top of main
.
Once my feat/JIRA-123
branch is ready to go into main
I might do a “Squash and Merge”. This will take my four commits in feat/JIRA-123
and turn them into a single commit when it hits main
. After the squash and merge my commit log in main
looks like:
commit 4106ee08a29ea773711811c85e2c1bd70d05ce31 (HEAD -> main, origin/main)
Author: David Newman <some@email.com>
Date: Fri May 6 14:08:07 2022 -0400
feat/JIRA-123
* chore: deprecate cross-compile for OS/360 and VAX 9000
* chore: makefile cross-compiles to OS/360 and VAX 9000
* fix: library no longer deletes root filesystem
* feat: initial code for new library
Now I want to get feat/JIRA-321
ready to go to main
and because I want to keep my job I make sure to test feat/JIRA-321
with the latest code from main
.
If I just do git rebase main
then I will have to deal with conflicts because the orignal commits from feat/JIRA-123
are still in the feat/JIRA-321
branch which makes git think that we are modifying the same files.
One way of avoiding the conflicts is:
git checkout main
git pull
git checkout feat/JIRA-321
git rebase --onto main HEAD~2
That last command is the important one and it says: “take two commits off the top of the currently checked out branch (feat/JIRA-321
) and place them on top of the git history of main
.
At this point any commits “between” the HEAD
commit of main and the HEAD~2
commits of feat/JIRA-321
are eliminated from the history of the feat/JIRA-321
branch. In this case, that’s fine, because we know that the HEAD
commit of main
contains all the same code/work/whatever from feat/JIRA-123
.
Now, this all goes out the window if I have been doing git merge
or git pull
between my two feature branches because I will have a non-linear commit history. However, the fact that the above is kinda simple once you know it is why I try and avoid that kind of thing at all costs.