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 either rebase or cherry-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

Days later

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:

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!