Red Green Repeat Adventures of a Spec Driven Junkie

More git merge vs. rebase

Continuing from my last article about git merge vs. rebase, one area merge and rebase greatly differ: combining work done at the same time using merge and rebase functions.

Pompeo della Cesa

source

Basically, merge interleaves work done at the same time. rebase takes the current branch’s changes and puts them on top.

If you want to follow along or check my work, repository used for this article is available here

From the master branch, let’s make a feature_ branch and put changes to the important file.

vagrant@ubuntu-xenial:/vagrant$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean
vagrant@ubuntu-xenial:/vagrant$ git checkout -b feature_1
Switched to a new branch 'feature_1'
vagrant@ubuntu-xenial:/vagrant$ ls
important_file.txt
vagrant@ubuntu-xenial:/vagrant$ echo "feature_1 commit 1" >> important_file.txt
vagrant@ubuntu-xenial:/vagrant$ git commit . -m 'FEATURE 1: commit 1'
[feature_1 d52ca2b] FEATURE 1: commit 1
 1 file changed, 1 insertion(+)

Let’s do the same for a fix_1 branch: From the master branch, create a new branch, but add changes to a file: fix_file.txt

vagrant@ubuntu-xenial:/vagrant$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
vagrant@ubuntu-xenial:/vagrant$ git checkout -b fix_1
Switched to a new branch 'fix_1'
vagrant@ubuntu-xenial:/vagrant$ echo "fix_1 commit 1" > fix_file.txt
vagrant@ubuntu-xenial:/vagrant$ git add fix_file.txt
vagrant@ubuntu-xenial:/vagrant$ git commit . -m 'FIX 1: commit 1'
[fix_1 62357c7] FIX 1: commit 1
 1 file changed, 1 insertion(+)
 create mode 100644 fix_file.txt

Now let’s add commits to both branches, alternating commits, so they will be:

  • commit change 2 to feature_1
  • commit change 2 to fix_1
  • commit change 3 to feature_1
  • commit change 3 to fix_1
vagrant@ubuntu-xenial:/vagrant$ git checkout feature_1
Switched to branch 'feature_1'
vagrant@ubuntu-xenial:/vagrant$ echo "feature_1 commit 2" >> important_file.txt
vagrant@ubuntu-xenial:/vagrant$ git commit . -m 'FEATURE 1: commit 2'
[feature_1 a323b14] FEATURE 1: commit 2
 1 file changed, 1 insertion(+)
vagrant@ubuntu-xenial:/vagrant$ git checkout fix_1
Switched to branch 'fix_1'
vagrant@ubuntu-xenial:/vagrant$ echo "fix_1 commit 2" > fix_file.txt
vagrant@ubuntu-xenial:/vagrant$ git commit . -m 'FIX 1: commit 2'
[fix_1 827052d] FIX 1: commit 2
 1 file changed, 1 insertion(+), 1 deletion(-)
vagrant@ubuntu-xenial:/vagrant$ git checkout feature_1
Switched to branch 'feature_1'
vagrant@ubuntu-xenial:/vagrant$ echo "feature_1 commit 3" >> important_file.txt
vagrant@ubuntu-xenial:/vagrant$ git commit . -m 'FIX 1: commit 3'
[feature_1 03ddc71] FIX 1: commit 3
 1 file changed, 1 insertion(+)
vagrant@ubuntu-xenial:/vagrant$ git commit -m 'FEATURE 1: commit 3' --amend --no-edit
[feature_1 617d588] FEATURE 1: commit 3
 Date: Mon May 13 19:08:33 2019 -0400
 1 file changed, 1 insertion(+)
vagrant@ubuntu-xenial:/vagrant$ git checkout fix_1
Switched to branch 'fix_1'
vagrant@ubuntu-xenial:/vagrant$ echo "fix_1 commit 3" > fix_file.txt
vagrant@ubuntu-xenial:/vagrant$ git commit . -m 'FIX 1: commit 3'
[fix_1 c08419e] FIX 1: commit 3
 1 file changed, 1 insertion(+), 1 deletion(-)

Whew, that’s a lot of work! Fixing and writing features at the same time. :-)

Log: feature_1

This is the log entries for the feature_1 branch:

617d588 (HEAD -> feature_1) FEATURE 1: commit 3
a323b14 FEATURE 1: commit 2
d52ca2b FEATURE 1: commit 1
c02bd5f (origin/master, master) FIX: Enhance important file
ec0dde3 FIX: Add important file contents
ef43cae FEATURE: include important file
597d771 initial commit

Yup, pretty straight forward.

Log: fix_1

This is the log entries for fix_1 branch:

vagrant@ubuntu-xenial:/vagrant$ git log --oneline
c08419e (HEAD -> fix_1) FIX 1: commit 3
827052d FIX 1: commit 2
62357c7 FIX 1: commit 1
c02bd5f (origin/master, master) FIX: Enhance important file
ec0dde3 FIX: Add important file contents
ef43cae FEATURE: include important file
597d771 initial commit

Again, straight forward log.

Now, let’s combine the fix_1 branch onto the feature_1 branch. We want to make sure our feature has all the fixes, right?

Merge fix onto feature

Let’s use the merge method to combine the fix_1 branch onto the feature_1 branch.

Let’s do this by creating a new branch from the feature_1 branch. By doing this change on another branch, name: merge_fix_to_feature, we can analyze the differences better:

vagrant@ubuntu-xenial:/vagrant$ git checkout feature_1
Switched to branch 'feature_1'
vagrant@ubuntu-xenial:/vagrant$ git checkout -b merge_fix_to_feature
Switched to a new branch 'merge_fix_to_feature'
vagrant@ubuntu-xenial:/vagrant$ git merge fix_1
Merge made by the 'recursive' strategy.
 fix_file.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 fix_file.txt

log results

Looking at the log results:

vagrant@ubuntu-xenial:/vagrant$ git log --oneline
02dd754 (HEAD -> merge_fix_to_feature) Merge branch 'fix_1' into merge_fix_to_feature
c08419e (fix_1) FIX 1: commit 3
617d588 (feature_1) FEATURE 1: commit 3
827052d FIX 1: commit 2
a323b14 FEATURE 1: commit 2
62357c7 FIX 1: commit 1
d52ca2b FEATURE 1: commit 1
c02bd5f (origin/master, master) FIX: Enhance important file
ec0dde3 FIX: Add important file contents
ef43cae FEATURE: include important file
597d771 initial commit

So the feature_1 commits and the fix_1 commits are in order with time.

Let’s see what happens when using a rebase strategy to combine the branches.

Rebase fix onto feature

Similar to the merge, let’s make a new branch: rebase_fix_to_feature so we can analyze differences:

vagrant@ubuntu-xenial:/vagrant$ git checkout feature_1
Switched to branch 'feature_1'
vagrant@ubuntu-xenial:/vagrant$ git checkout -b rebase_fix_to_feature
Switched to a new branch 'rebase_fix_to_feature'

Now let’s rebase!

vagrant@ubuntu-xenial:/vagrant$ git rebase fix_1
First, rewinding head to replay your work on top of it...
Applying: FEATURE 1: commit 1
Applying: FEATURE 1: commit 2
Applying: FEATURE 1: commit 3

Oh, not even a commit message needed? That was fast & easy.

Let’s look at the log results:

vagrant@ubuntu-xenial:/vagrant$ git log --oneline
caf8ebe (HEAD -> rebase_fix_to_feature) FEATURE 1: commit 3
2056799 FEATURE 1: commit 2
0d96b8f FEATURE 1: commit 1
c08419e (fix_1) FIX 1: commit 3
827052d FIX 1: commit 2
62357c7 FIX 1: commit 1
c02bd5f (origin/master, master) FIX: Enhance important file
ec0dde3 FIX: Add important file contents
ef43cae FEATURE: include important file
597d771 initial commit

This time, all the feature_1 commits are on top of the fix_1 commits. All the feature_1 commit values are different from the original:

commit SHA - feature_1 entry commit SHA - rebase_fix_to_feature entry
617d588 (HEAD -> feature_1) FEATURE 1: commit 3 caf8ebe (HEAD -> rebase_fix_to_feature) FEATURE 1: commit 3
a323b14 FEATURE 1: commit 2 2056799 FEATURE 1: commit 2
d52ca2b FEATURE 1: commit 1 0d96b8f FEATURE 1: commit 1

So, git created new commits for all the work on the feature_1 when putting the fix_1 work underneath to incorporate the changes.

Graph

One more thing: let’s look at the differences of the log graph

rebase_fix_to_feature

* caf8ebe (HEAD -> rebase_fix_to_feature) FEATURE 1: commit 3
* 2056799 FEATURE 1: commit 2
* 0d96b8f FEATURE 1: commit 1
* c08419e (fix_1) FIX 1: commit 3
* 827052d FIX 1: commit 2
* 62357c7 FIX 1: commit 1
* c02bd5f (origin/master, master) FIX: Enhance important file
* ec0dde3 FIX: Add important file contents
* ef43cae FEATURE: include important file
* 597d771 initial commit

merge_fix_to_feature

Andrews-MacBook:merge_vs_rebase andrew$ git log --graph --oneline
*   02dd754 (HEAD -> merge_fix_to_feature) Merge branch 'fix_1' into merge_fix_to_feature
|\
| * c08419e (fix_1) FIX 1: commit 3
| * 827052d FIX 1: commit 2
| * 62357c7 FIX 1: commit 1
* | 617d588 (feature_1) FEATURE 1: commit 3
* | a323b14 FEATURE 1: commit 2
* | d52ca2b FEATURE 1: commit 1
|/
* c02bd5f (origin/master, master) FIX: Enhance important file
* ec0dde3 FIX: Add important file contents
* ef43cae FEATURE: include important file
* 597d771 initial commit

Huh?? What’s going on?? The commits look like they are in order.

A merge commit, 02dd, points to fix_1 and feature_1, which shows their commits together, but in reality, interleaved with each other when reviewing linearly:

vagrant@ubuntu-xenial:/vagrant$ git log --oneline
02dd754 (HEAD -> merge_fix_to_feature) Merge branch 'fix_1' into merge_fix_to_feature
c08419e (fix_1) FIX 1: commit 3
617d588 (feature_1) FEATURE 1: commit 3
827052d FIX 1: commit 2
a323b14 FEATURE 1: commit 2
62357c7 FIX 1: commit 1
d52ca2b FEATURE 1: commit 1
c02bd5f (origin/master, master) FIX: Enhance important file
ec0dde3 FIX: Add important file contents
ef43cae FEATURE: include important file
597d771 initial commit

Conclusion

When combining branches, I highlight the difference between the merge and rebase funtions when making commits simultaneously on branches.

The merge branch function preserves the original commits and order.

The rebase branch function takes changes from the current branch and puts them on top of the changed branch.

Both methods are valid way to combine changes between branches in git. Merge produces a history that is accurate with respect to work. Rebase produces a linear history based on the branch.

I prefer a linear history, so I tend to use rebase over merge when combining branches.