Red Green Repeat Adventures of a Spec Driven Junkie

git: merge vs. rebase

After my lunch & learn talk, I realized I did not go over the difference between rebase and merge, two techniques to get work into a branch from another.

Abstract Portrait Vessel of a Ruler with Head reasing on Legs

source

Suppose you have a repo with two branches:

  • master
  • testing

The repository is available here

The main difference between testing & master branches:

  • ec0d: common parent to both creates the file: important_file.txt
  • c02b: master adds a line to the important_file.txt
  • c2a5: testing adds a new file: testing_file.txt

Details of common parent to master and testing: ec0d

vagrant@ubuntu-xenial:/vagrant$ git show ec0d
commit ec0dde385a2d36db33451ec977d796d0c03cc6b4 (HEAD)
Author: Andrew Leung <andrew@ualberta.net>
Date:   Thu Apr 18 07:54:57 2019 -0400

    FIX: Add important file contents

diff --git a/important_file.txt b/important_file.txt
index e69de29..0b438af 100644
--- a/important_file.txt
+++ b/important_file.txt
@@ -0,0 +1,3 @@
+IMPORTANT FILE
+
+DO NOT ERASE

Details of the commit added to master: c02b

vagrant@ubuntu-xenial:/vagrant$ git show c02b
commit c02bd5fa53e0513d91b0e2545573bb8f18acb9a5 (master)
Author: Andrew Leung <andrew@ualberta.net>
Date:   Thu Apr 18 07:59:12 2019 -0400

    FIX: Enhance important file

diff --git a/important_file.txt b/important_file.txt
index 0b438af..652404b 100644
--- a/important_file.txt
+++ b/important_file.txt
@@ -1,3 +1,5 @@
 IMPORTANT FILE

 DO NOT ERASE
+
+REALLY DO NOT ERASE

Details of the one commit added to testing: c2a5

vagrant@ubuntu-xenial:/vagrant$ git show c2a5
commit c2a5c4a7b45305c52dc6c729d813974689391cc9 (testing)
Author: Andrew Leung <andrew@ualberta.net>
Date:   Thu Apr 18 07:58:12 2019 -0400

    FEATURE: Add testing file

diff --git a/testing_file.txt b/testing_file.txt
new file mode 100644
index 0000000..884c301
--- /dev/null
+++ b/testing_file.txt
@@ -0,0 +1,3 @@
+TESTING FILE
+
+ONLY FOR TESTING PURPOSES

Summary of repository branches:

  • ec0d: common parent, creates important file
  • c02b: master branch - extends important file
  • c2a5: testing branch - adds testing file

Let’s get changes from testing (c2a5) onto master (c02b) using the two common ways in git: merge and rebase.

Merge

Let’s get changes from the testing branch, c2a5, onto the master branch, c02b by merging, and using a different branch aptly named: merge_testing_to_master:

vagrant@ubuntu-xenial:/vagrant$ git checkout master
Switched to branch 'master'
vagrant@ubuntu-xenial:/vagrant$ git checkout -b merge_testing_to_master
Switched to a new branch 'merge_testing_to_master'
vagrant@ubuntu-xenial:/vagrant$ git merge testing
# make user entry for merge
Merge made by the 'recursive' strategy.
 testing_file.txt | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 testing_file.txt

Let’s look at the result via $ git log --graph:

vagrant@ubuntu-xenial:/vagrant$ git log --graph --oneline
*   ea824a6 (HEAD -> merge_testing_to_master) Merge branch 'testing' into merge_testing_to_master
|\
| * c2a5c4a (testing) FEATURE: Add testing file
* | c02bd5f (master) FIX: Enhance important file
|/
* ec0dde3 FIX: Add important file contents
* ef43cae FEATURE: include important file
* 597d771 initial commit

Notice that the new merge commit, ea82, points to the testing branch, c2a5, and master branch, c02b.

Rebase

Now, let’s do the same thing: get changes onto the testing branch, c2a5, onto the master branch, c02b, using the rebase technique on a new branch, aptly named: rebase_testing_to_master:

vagrant@ubuntu-xenial:/vagrant$ git checkout master
Switched to branch 'master'
vagrant@ubuntu-xenial:/vagrant$ git checkout -b rebase_testing_to_master
Switched to a new branch 'rebase_testing_to_master'
vagrant@ubuntu-xenial:/vagrant$ git rebase testing
First, rewinding head to replay your work on top of it...
Applying: FIX: Enhance important file
vagrant@ubuntu-xenial:/vagrant$

Let’s take a look at the result using $ git log --graph:

vagrant@ubuntu-xenial:/vagrant$ git log --graph --oneline
* e4607f4 (HEAD -> rebase_testing_to_master) FIX: Enhance important file
* c2a5c4a (testing) FEATURE: Add testing file
* ec0dde3 FIX: Add important file contents
* ef43cae FEATURE: include important file
* 597d771 initial commit

Notice that there is no commit between the master and testing branches as in the merge technique. The master commit, c02b, does not appear in this branch’s history, BUT there is a new commit, e460, which has the same commit message as the, c02b master commit:

FIX: Enhance important file

The details of the e460 change is:

vagrant@ubuntu-xenial:/vagrant$ git show e460
commit e4607f487df0159b33001067e999aa1479b944da (rebase_testing_to_master)
Author: Andrew Leung <andrew@ualberta.net>
Date:   Thu Apr 18 07:59:12 2019 -0400

    FIX: Enhance important file

diff --git a/important_file.txt b/important_file.txt
index 0b438af..652404b 100644
--- a/important_file.txt
+++ b/important_file.txt
@@ -1,3 +1,5 @@
 IMPORTANT FILE

 DO NOT ERASE
+
+REALLY DO NOT ERASE

Notice that this new commit has exactly the same contents as the original master commit, c02b:

vagrant@ubuntu-xenial:/vagrant$ git show c02b
commit c02bd5fa53e0513d91b0e2545573bb8f18acb9a5 (master)
Author: Andrew Leung <andrew@ualberta.net>
Date:   Thu Apr 18 07:59:12 2019 -0400

    FIX: Enhance important file

diff --git a/important_file.txt b/important_file.txt
index 0b438af..652404b 100644
--- a/important_file.txt
+++ b/important_file.txt
@@ -1,3 +1,5 @@
 IMPORTANT FILE

 DO NOT ERASE
+
+REALLY DO NOT ERASE

Even down to the file change: index 0b438af..652404b 100644

Differences

The $ git diff of the branches produces no differences:

vagrant@ubuntu-xenial:/vagrant$ git diff merge_testing_to_master rebase_testing_to_master
vagrant@ubuntu-xenial:/vagrant$

History Difference

The only place where there is a difference is the history of the branches, the merge technique introduced a new merge commit, ea82, while the rebase technique changed the master commit from: c02b to e460.

The rebase took the work of the testing branch, c2a5, and put it underneath the current master branch, c02b, and making a new commit for it, e460.

Big Picture

Taking a look at all the branches using $ git log --graph --all --oneline shows:

vagrant@ubuntu-xenial:/vagrant$ git log --graph --all --oneline
* e4607f4 (rebase_testing_to_master) FIX: Enhance important file
| *   ea824a6 (merge_testing_to_master) Merge branch 'testing' into merge_testing_to_master
| |\
| |/
|/|
* | c2a5c4a (testing) FEATURE: Add testing file
| * c02bd5f (master) FIX: Enhance important file
|/
* ec0dde3 (HEAD) FIX: Add important file contents
* ef43cae FEATURE: include important file
* 597d771 initial commit

Both branches share a common parent in current HEAD, ec0d, but where they end up afterwards is slightly different, both produce their own path forward:

  • rebase made everything linear by recommiting the master work on top of the testing work
  • merge created a new commit, referencing both master and testing

Options, options

Which one should you use? I prefer to rebase everything so the work of the branch HEAD points to is ahead of everything else. I am a bit annoyed with merges since it creates another commit.

Conclusion

To get work from another branch onto the current branch, there are git rebase and git merge.

Rebasing creates a new commit of the current work and “slides” all the other branch’s work underneath. Keeping history in a linear fashion.

Merging creates a new commit that points to both the current branch and the other branch’s work.

Which one to use is a personal preference, but I tend to prefer rebasing over merging, only to keep history clean.