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