Skip to content
stoic man
June 10, 2018

Using Git rebase

Updating and merging your branch properly

Back to when I started using Git, I was faced with this challenge. Someone has updated the upstream - how do I incorporate the latest changes into my working branch?

For the sake of simplicity, the upstream will be master and the working branch working_branch.

The obvious route is to pull master and merge it into the working branch. So something like:

Terminal
git pull origin master

Unfortunately, this way we're inevitably creating a new commit.

Working with a team that provides frequent updates while we're slowly working on our feature, we may end up with quite a lot of noise in the git graph.

What we really want in this scenario is the following:

  1. Isolate the local commits
  2. Get the latest upstream updates
  3. Apply the changes on top
  4. Resolve any conflicts

So we don't have to merge our branch in the updated upstream but rebase it.

First, let's fast-forward the local master branch for good measure.

Terminal
git checkout master
 
git pull

Now let's head back to our working branch and do a rebase. All of the local commits will be applied on top of the updated master

Terminal
git checkout working_branch
 
git rebase origin master

You can completely ignore updating your local master branch and simply run git rebase origin/master.

I like having my local master up to date so I opt for the first case.

At this point, everything will either run smoothly or some conflicts will arise.

  1. git rebase --abort will completely stop the rebase. The branch's initial state will be restored.
  2. git rebase --skip will ignore the local commit that causes the problem - never used it, to be honest
  3. Fix the conflicts, run git add/rm <files_to_fix> to mark them as resolved and keep the rebase going with git rebase --continue

Merging your branch

Having finished the work, it's always a nice idea to 'squash' commits that are meant to fix previous commits. This way we can automatically generate a Changelog out of the commits and have a tidy git log.

For this reason, doing an 'Interactive' rebase is really helpful

Terminal
git rebase -i master

Here's an example of an interactive rebase prompt. The 'formm' typo is intentional.

Terminal
pick 07c6add Include password strength indicator in registration formm
pick d29b1fb Fix the regex
pick 317tt36 Minor typo
pick fa2fff3 CSS tweaks
 
# Rebase 44a7ee6..fa2fff3 onto 44a7ee6
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

Now you can do any of the following

  1. Pick - Keep the commit
  2. Reword - Keep the commit, get a prompt to update the message
  3. Edit - Keep the commit, allows to modify files
  4. Squash - Merge into the commit above, get a prompt to update the message
  5. Fixup - Same as Squash but won't prompt for a new message
  6. Exec - Allows running shell commands against a commit
You can reorder these commits around if needed.

For this case, we'll

  1. Reword and keep the first commit
  2. Squash the rest and discard the commit message by doing a Fixup
Terminal
r 07c6add Include password strength indicator in registration formm
f d29b1fb Fix the regex
f 317tt36 Minor typo
f fa2fff3 CSS tweaks

Consequently, our branch will contain a single meaningful commit on top of all the latest upstream changes. We push, open a pull request, get some code reviews and when everything is said and done, merge working_branch into master.

We only use rebase to keep our local branch up-to-date. Don't rebase branches where others are collaborating with you.

Some tips

Backup

It's always helpful to keep a backup of your working branch if you don't feel confident about the rebase.

In the case of not having pushed your branch in the remote you can't do a git reset --hard origin/working_branch if you mess up, so keep a backup:

Terminal
git checkout -b backup/working_branch
 
git checkout working_branch

Do the rebase process, and if you mess up reset like this:

Terminal
git reset --hard backup/working_branch

Rewrite history safely

Say we have opened a pull request but some people have requested changes. We implement them, do another rebase to incorporate the latest changes and do a git push origin working_branch

Unfortunately our changes will be rejected! By doing a rebase, each commit is considered new, and have different hashes. The remote informs you that there are commits (the ones with the old hashes) that are not present in your local branch, so you have to do an update first. That's not the case. We have rewritten history and would rather override everything that is on the remote. Essentially we have to do a force push.

There is a little caveat with that though. If someone has pushed a commit into the branch before us, and we do a force push - it will forever be gone.

It's best to do a --force-with-lease, that will essentially prevent the remote from being updated if someone else has done some work.

Terminal
git push --force-with-lease origin working_branch