1st March, 2017

Git Rebase

What is a rebase?

Assume the following git history where you are working on the my-feature branch.

          A---B---C my-feature
         /
    D---E---F---G master

After git rebase master this would become:

                  A--B--C my-feature
                 /
    D---E---F---G master

Workflow for Single Remote

This is assuming:

  1. Everyone on your team is working from one repository.
  2. You are currently on a feat/chore/fix branch and not on master.
git fetch origin
git rebase -i origin/master

At this point the editor will launch and will allow you to pick commits for squashing:

rebase starts

We are going to squash all commits into one. Leave the first commit untouched and convert all the other pick into squash or s. Save and close the editor window.

squash the commits

If no conflicts were encountered the rebase will complete and you move to the final step. Your editor will open up again. This time it shows you the messages from the various commits that are going to be squashed. Here you get a chance to compose the message for the new squashed commit.

all the commit messages

Save and close the editor window once you have composed a new git message.

new commit message

At this point the rebase is complete 🎉 Last step, push your changes to remote.

$ git push origin my-feature -f

Yes, that’s a force push there. Because you have successfully re-written git history. Where you previously had multiple commits you now only have one commit.

Resolving Conflicts

During the rebase process you might encounter conflicts.

conflitc

Relax. Take a deep to breath. We can deal with this 💪

Start by running git status this will show you a list of files that have conflicts. Go through them one by one and resolve conflicts.

The conflicts show up something like this (there can be more than one in a file):

  This is the first line
  This is the second line
  This is the third line I added
<<<<<<< HEAD
  Someone else added this line
  Someone else added this line too
=======
  This is the fourth line I added
  This is the fifth line I added
>>>>>>> f78d8ae... <commit message>

Here everything between <<<<<<< HEAD and ======= is stuff that is in the master branch. And everything between ======= and >>>>>>> f78d8ae... <commit message> is your on the my-feature branch. Git could not automatically merge these so it wants you to decide how these lines should be merged.

You can pick master version or your version depending on what you were implementing. You are the best judge here. Sometimes the correct answer will be a combination of the two.

Pick the appropriate combination and delete the conflict markers (<<<<<<< HEAD, =======, etc).

  This is the first line
  This is the second line
  This is the third line I added
  Someone else added this line
  This is the fourth line I added
  This is the fifth line I added

Do the same for all the other files with conflicts. Then:

  1. Stage the changes by executing git add <file-name>
  2. Continue the rebase git rebase --contine

If you messed something up you can always git rebase --abort. Remember to rebase early and often. This will prevent messy merge conflicts.

Workflow for Multiple Remotes

This is assuming:

  1. There is a main repository that you can only update through pull requests. We will call this remote upstream.
  2. You create a fork of this repository and clone that locally. We will call your forked remote origin.

The setup process will be something like this:

$ git clone git@github.com:[your user name]/[your repo].git
$ cd [your repo]
$ git remote add upstream git@github.com:rangle/[your repo].git

The only difference between this setup and previous is that you will be fetching upstream and rebasing against upstream/master. Then push to your forked origin. The actual rebase & conflict resolution process is the same.

Let us break this down step-by-step:

# (1) Start by updating your fork from the main repo.
# At this point you are on the master branch.
$ git fetch upstream
$ git rebase upstream/master

# (2) Create a new branch for your feature
$ git checkout -b my-feature

# (3) Implement the feature and commit your changes as usual.
# This might be single or multiple commits.

# (4) Push your changes to origin
$ git push origin my-feature

# (5) Before we rebase we must get the latest upstream
$ git fetch upstream

# (6) Next, do the actual rebase. This point onwards it is the
# same process as single remote workflow above.
$ git rebase upstream/master

# (7) Push your changes to origin not upstream!
$ git push origin my-feature -f

Github

If you are using github then a lot of this stuff can be done through their GUI. Read more about it here. github squash and rebase features

Undo

Scenario: I messed up conflict resolution during a rebase, pushed changes to remote and lost hours of work 😭

i've made a huge mistake

Use git reflog to undo your rebase. Running the command will show you the reflog for your local repository. For example:

adc164e HEAD@{0}: rebase -i (finish): returning to refs/heads/my-feature
adc164e HEAD@{1}: rebase -i (pick): Bugfix: SVG icons on android
8ab621c HEAD@{2}: rebase -i (start): checkout master
067be75 HEAD@{3}: checkout: moving from master to my-feature
8ab621c HEAD@{4}: pull: Fast-forward

In this scenario to undo my rebase I need to run git reset --hard HEAD@{3} because that was the last state before I started the rebase.