Saturday, 23 January 2016

Git - Rebase

Merging branches in git happens in one of the following two fashions.
  1. Fast Forward Merge: It occurs when the commit histories of two branches are linear. It doesn't introduce a new commit.
  2. 3-Way Merge: When the commit histories of the branches are diverged then the last commits of each branch are merged by introducing a new commit.
What if you want to perform a Fast Forward merge with a diverged commit history? The solution is Rebase.  

Illustrating a Rebase

Consider following diagram representing current state of repository's commit history.
Repo Commit History
Merging feature with master will invoke 3-way merge and introduce a new commit as follows.

3-Way Merge
 
To avoid a 3-way, I'll first perform rebase for feature -
$ git checkout feature
$ git rebase master
Repo Commit History - After Rebase
 It has changed the base of feature branch. Repo's commit history depicts that feature branch has been created from master's commit 4. Now a merge in master will be a fast forward merge.
$ git checkout master
$ git merge feature
Fast Forward Merge - After Rebase
We successfully performed rebasing of feature branch onto master. 

How git performs a Rebase?

1. Find common ancestor of master and feature (i.e. 2) by rewinding HEAD of feature
2. Store all changes post commit 2 viz. A, B in a temp location
3. Make HEAD of feature point to master
4. Replay commits A, B on top of master
5. Make HEAD of feature point to tip of commits

If there are any conflicts you need to resolve that and continue with rebase. 
$ git rebase --continue

And if you find rebasing making a mess in the repo, you can abort it too.
$ git rebase --abort









Advanced Rebasing

Consider following as present state of repo history graph.

Now I want to merge commits introduced ONLY by client branch with master. What you think?
$ git checkout master
$ git merge client

NO! It will be a 3-way merge and will also have S1, S2, S3 along with C1, C2.


What we want is -

& following commands will help to achieve this.
$ git rebase --onto master server client
$ git checkout master
$ git merge client

Following are the steps performed for  
$ git rebase --onto master server client
  1. Checkout client branch
  2. Find common ancestor of server and client i.e. S3
  3. Store all commits on client post common ancestor in a temp location viz. C1, C2
  4. Make HEAD of client point to master
  5. Replay commits stored in temp location on top of master
  6. Make HEAD of client point to tip of the commits
A merge between master and server will again be 3-way. I can avoid that and do a rebase without checking out server branch.
Syntax - $ git rebase <base_branch> <topic_brach>
Command - $ git rebase master server


And now you can do a fast forward merge with master.
$ git checkout master
$ git merge server

Notice that the final commit graph is not representing actual state of when from which commit of which branch which other branch is checked out. But it gives a clean view of commit history by means of rebase manipulation. And you doesn't loose your work at all.


Caution Notes:
1. Don't rebase commits which are not yours.
2. Don't rebase your own commits if you have pushed on server and someone has already pulled it in their repo.

Why Rebase

Rebase is not a mandatory action to perform in a usual git workflow. But you can use it to keep your commit history clean. Otherwise 3-way merges will keep on cluttering your commit history with extra commits. Only perform rebase for commits which you own and haven't shared with others.

No comments:

Post a Comment

Your comments are very much valuable for us. Thanks for giving your precious time.