Git rebase guide
This is my guide for rebase, I’ll explain my process for rebasing and an alternative using cherry-pick that I often use when working on Open Source software. This guide is usefull when a maintainer says “please rebase” and you end up with 40 commits not being yours in the github pull request.
First there is a thing I never use which is: git pull
. The manual says:
Incorporates changes from a remote repository into the current branch. In its default
mode, git pull is shorthand for git fetch followed by git merge FETCH_HEAD.
Over this guide we’ll prefer a single
git fetch
as we will use eitherrebase
orcherry-pick
.
To understand this, you first need to know about remotes
. A complete explanation for this is available in the git book and I’ll just cover the basics. When you clone a repository, by default you will end up with a single remote
called origin
. Find this out using:
git remote -v
You can add, rename, change the url of the remotes using that same command (see man git remote
). By default, you also end up on the main
(or master
) branch which is tight to the origin
remote, git calls this “tracking”. A schema for this first state would look like:
A---B---C main on origin
\
A---B---C main
^
origin/main in your repository
Now let’s fork the repository and add a new remote, the best practice is to rename origin
to upstream
and to add yours:
git remote rename origin upstream
git remote add origin git@github.com:soyuka/repository
A schema of the main branch could look like:
A---B---C main on upstream
\
A---B---C main on origin
\
A---B---C main
^
origin/main in your repository
# Git rebase
You have worked, commit, pushed and it’s not possible to merge, the status of the branch graph might look like this locally:
A---B---C main on upstream
\
C---D---E main on origin
\
C---D---E main
^
origin/main in your repository
Wait, it has not changed for upstream, let’s use git fetch
to retreive the changes:
A---B---C---F---G---H main on upstream
\
C---D---E main on origin
\
C---D---E main
What the maintainer would probably want you to do is to get back these F, G and H commit on your branch. To do so let’s first do a rebase using:
git rebase upstream/main
You’re done, if there are conflicts read the instructions which are:
- fix the conflict
git add
the resolved filesgit rebase --continue
Note that if you think there are too many conflicts, you can git rebase --abort
and we’re going to use another technique. Sometimes you need to change the target branch and the rebase command would be git rebase upstream/branch-name
.
# Cherry-pick
Cherry-picking is a wonderful, underrated feature of git which can also help in cases like this one. Let’s say that H
has conflicts with F
, when rebasing you also end up resolving these conflicts although they do not concern your work:
A---B---C---F---G---H main on upstream
\
F---G---H (conflict) main
\
C---D---E temporary area
As a reminder (from the man git rebase
page):
All changes made by commits in the current branch but that are not in
are saved to a temporary area. (…) The commits that were previously saved into the temporary area are then reapplied to the current branch, one by one, in order.
To do the same using cherry-pick
we’re going to first squash our commits into a single one (C, D, E will end up in I). Let’s git rebase --abort
and rebase interactively:
git rebase -i HEAD~3
Will open an editor where you can re-order, rename, squash commits (everything is explained in a comment) and it may look like this:
pick c1234567 the C commit message
pick d1234567 the D commit message
pick e1234567 the E commit message
Let’s change this to:
reword c1234567 the C commit message
fixup d1234567 the D commit message
fixup e1234567 the E commit message
And reword the C
commit to the I commit message
. After this, the graph will be:
A---B---C---F---G---H main on upstream
\
I main
Instead of rebasing, let’s reset the main
branch to the tracked one upstream/main
and just cherry-pick our I
commit. The commit sha1
can be first copied using:
git rev-parse HEAD
# on os x for example:
git rev-parse HEAD | pbcopy
then use:
git reset --hard origin/main
git cherry-pick i123456049fca042c6aa2526a2ed657fc5a20bd
Cherry picking also works with commit ranges and may cause less conflicts, at least no conflicts from code that is not yours. Check the man git cherry-pick
for more informations!