Elentok's Blog

About me

Git Rebase Example

What is rebase?

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.

Example

Let's look at this scenario:

  1. 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
    
  2. User B works on the project and makes two commits, so his repository looks like this:

      $ git log
    
      07c8c6d - **(HEAD, master)** added file4 
      e404363 - 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).

  3. User B pushes the changes to origin (the original repository):

      $ git push
      $ git log
    
      07c8c6d - **(HEAD, origin/master, origin/HEAD, master)**
        added file4 
      e404363 - added file3 
      19abe80 - added file2
      8cb6ba4 - added file1
    
  4. At the same time, User A makes two commits, so his repository looks like this:

      $ git log
    
      ef5b54f - **(HEAD, master)** added file6 
      5f7cff8 - added file5 
      19abe80 - **(origin/master, origin/HEAD)** added file2
      8cb6ba4 - added file1
    
  5. 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.

  6. 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.

  7. 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
    
  8. 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 :-)

What happens when there are conflicts?

Let's look at a similar scenario:

  1. 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 
    ...
    
  2. 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 
    ...
    
  3. 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
    
  4. 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
    
  5. 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 
    ...
    
  6. 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.

Next:How do I ??? with Git?