Rebase means taking commits to made to an older revision of a branch and applying them to a newer revision of that branch (usually when the origin branch has changed while we were commiting).
Rebase is an alternative for merge, it's main advantage over merging is that it allows you to maintain a clean linear history.
Let's look at this scenario:
User A and User B both clone the same repository. At the moment of cloning the repository had 2 commits:
$ git log 19abe80 - **(HEAD, master)** added file2 8cb6ba4 - added file1
User B works on the project and makes two commits, so his repository looks like this:
$ git log 07c8c6d - **(HEAD, master)** added file4e404363 - added file3 19abe80 - **(origin/master, origin/HEAD)** added file2 8cb6ba4 - added file1
As you can see, User B's master branch is ahead of 'origin/master' by 2 commits ('origin/master' is the master branch in the origin repository).
User B pushes the changes to origin (the original repository):
$ git push $ git log 07c8c6d - **(HEAD, origin/master, origin/HEAD, master)** added file4e404363 - added file3 19abe80 - added file2 8cb6ba4 - added file1
At the same time, User A makes two commits, so his repository looks like this:
$ git log ef5b54f - **(HEAD, master)** added file65f7cff8 - added file5 19abe80 - **(origin/master, origin/HEAD)** added file2 8cb6ba4 - added file1
When User A tries to push his changes, he gets this error:
$ git push ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to '...' To prevent you from losing history, non-fast-forward updates were rejected Merge the remote changes (e.g. 'git pull') before pushing again. See the 'Note about fast-forwards' section of 'git push --help' for details.
Git notices that the master branch in the origin repository no longer matches 'origin/master' so it won't allow the push.
To solve this, User A first fetches the changes from the origin repository:
$ git fetch $ git log --graph --all * ef5b54f - **(HEAD, master)** added file6* 5f7cff8 - added file5 | * 07c8c6d - **(origin/master, origin/HEAD)** added file4 | * e404363 - added file3 |/ * 19abe80 - added file2 * 8cb6ba4 - added file1
Adding --all to the log command shows commits from all of the branches and adding --graph shows a graphic representation of the branches.
The second step is to perform a rebase that will update the new commits by User A (ef5b54f and 5f7cff8) so it will appear as if they were commited after the new 'origin/master' (07c8c6d).
Since User A and User B's commits involve different files there won't be any conflicts (I will talk about conflicts later on).
$ git rebase origin/master First, rewinding head to replay your work on top of it... Applying: added file5 Applying: added file6 $ git log --graph --all * 4fca7e8 - **(HEAD, master)** added file6* 29ea4d2 - added file5 * 07c8c6d - **(origin/master, origin/HEAD)** added file4 * e404363 - added file3 * 19abe80 - added file2 * 8cb6ba4 - added file1
Now User A can push his changes to the origin repository:
$ git push $ git log --graph --all * 4fca7e8 - **(HEAD, origin/master, origin/HEAD, master)** added file6 * 29ea4d2 - added file5* 07c8c6d - added file4 * e404363 - added file3 * 19abe80 - added file2 * 8cb6ba4 - added file1
We now have a clean, linear history :-)
Let's look at a similar scenario:
User B added a line to file4.txt, commited and pushed:
$ git push $ git log --graph --all * e1e4730 - **(HEAD, origin/master, origin/HEAD, master)** added a line to file4 (2 minutes)* 4fca7e8 - added file6 * 29ea4d2 - added file5 ...
At the same time User A added a line to file4.txt, and commited:
$ git log --graph --all * 77a6486 - **(HEAD, master)** added another line to file4* 4fca7e8 - **(origin/master, origin/HEAD)** added file6 * 29ea4d2 - added file5 ...
When User A tries to push the changes, he gets the same error we have seen before. So, he fetches the changes and tries to rebase:
$ git fetch $ git rebase origin/master _First, rewinding head to replay your work on top of it... Applying: added another line to file4 Using index info to reconstruct a base tree... Falling back to patching base and 3-way merge... **Auto-merging file4.txt CONFLICT (content): Merge conflict in file4.txt Failed to merge in the changes. Patch failed at 0001 added another line to file4** When you have resolved this problem run "git rebase --continue". If you would prefer to skip this patch, instead run "git rebase --skip". To restore the original branch and stop rebasing run "git rebase --abort"._
While trying to auto merge file4.txt git reached a conflict and paused the rebase.
If we look at file4.txt we will see the following contents:
file4 <<<<<<< HEAD added a line to file4 ======= added another line to file4 >>>>>>> added another line to file4
This is how git handles conflicts, now User A can open his favorite editor (vim) and merge the file manually:
file4 added a line to file4 added another line to file4
After merging the file, User A must mark the conflict as solved (by using "git add") and continue the rebase:
$ git add file4.txt $ git rebase --continue * c8c37cd - **(HEAD, master)** added another line to file4* e1e4730 - **(origin/master, origin/HEAD)** added a line to file4 * 4fca7e8 - added file6 ...
User A can now push the changes back to origin:
$ git push * c8c37cd - **(HEAD, origin/master, origin/HEAD, master)** added another line to file4* e1e4730 - added a line to file4 * 4fca7e8 - added file6 ...
And again, we get a clean, linear history.
I hope you find this post useful.
Until the next time, David.