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.
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.txtc02b
: master adds a line to the important_file.txtc2a5
: 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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 filec02b
: master branch - extends important filec2a5
: 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 <[email protected]>
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 <[email protected]>
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.