Introduction
I've seen many developers avoid using rebase due to its complexity. I've also noticed that some teams even forbid rebase in their projects altogether.
In this guide, we will walk through the basics of managing code changes in Git using both merge and rebase strategies. This tutorial aims to clarify these often misunderstood commands with a hands-on demonstration.
I may be too verbose in walking you through these steps one by one, but I suggest you start from the Recap section, or jump straight to the Key Takeaways section.
Table of Contents
- Introduction
- Scenario Setup
- Initial Git Commit Graph
- Decision Time: Merging vs. Rebasing
- Option 1: Merge feature into main
- Option 2: Rebase feature onto main
- Recap
- Key Takeaways: Merge or Rebase?
Scenario Setup
Initialise a Git Repository and Create a Starting File
First, create a new directory and initialise a Git repository:
mkdir git-nuance-demo-merge && \
cd git-nuance-demo-merge && \
git init && \
git branch -M main
Next, create a text file named example.txt
:
touch example.txt
Make Initial Commits A
and B
Now, add a first line to example.txt
and commit it as Commit A
:
+Initial content
Proceed by adding a second line and commit it as Commit B
:
Initial content
+Added by leader
Bseline Commit Graph
Now we can visualize the project's baseline with this commit graph:
A---B [main]
Create a Feature Branch
Assuming developer Kev needs to add a new feature, he branches off from main
:
git checkout main && \
git checkout -b feature
Commit C
While Kev works on the feature branch, another developer, Dash, updates the main
branch. Dash modifies the second line in Commit C
:
git checkout main
Initial content
-Added by leader
+Modified by Dash
Commit D
(Developing on Feature Branch)
Simultaneously, Kev modifies the same line on his branch in Commit D
:
git checkout feature
Initial content
-Added by leader
+Modified by Kev
Initial Git Commit Graph
The commit graph is now:
A---B---C [main]
\
D [feature]
And check their Git history details by running git log main
and git log feature
:
The commit IDs (hashes) will be different if you try following my steps from the beginning on your device.
main
/workspaces/git-nuance-demo-merge (main) $ git log main
commit ac466bea2d13762608660e27798ba08b840529db (HEAD -> main)
Author: Dash <dash@test.com>
C
commit fd6ecc11625c41c06e150015254b16a620c1cd44
Author: Leader <leader@test.com>
B
commit 9d00689a138f8d1fd6b7d370bae29bcfb27fec19
Author: Leader <leader@test.com>
A
feature
/workspaces/git-nuance-demo-merge (main) $ git log feature
commit 58beb1e4f1d47f9bf1364be906a2e22b5280be1d (HEAD -> feature)
Author: Kev <kev@test.com>
D
commit fd6ecc11625c41c06e150015254b16a620c1cd44
Author: Leader <leader@test.com>
B
commit 9d00689a138f8d1fd6b7d370bae29bcfb27fec19
Author: Leader <leader@test.com>
A
Decision Time: Merging vs. Rebasing
Imagine Kev needs to release the features in branch feature
, he faces a decision: to merge or rebase his feature branch onto the updated main branch.:
-
Merge
: mergefeature
intomain
and releasemain
to production environment. -
Rebase
: Rebasefeature
ontomain
, and releasefeature
.
We all know that Kev and Dash both edited the second line, meaning a conflict is definitely going to happen in both options.
Next, we're going to apply both options and examine the different consequences of each.
Before continuing, copy the initial status into two separate folders so that we can apply each option in its own folder:
cd .. && \ cp -r git-nuance-demo-merge git-nuance-demo-rebase
Option 1: Merge feature
into main
Merge
Merging results in a new commit on main
that combines the changes from both branches:
cd git-nuance-demo-merge
git checkout main
git merge feature
Merge Conflict
This approach creates a merge conflict that Kev resolves by favouring his changes:
Initial content
<<<<<<< HEAD
Modified by Dash
=======
Modified by Kev
>>>>>>> feature
Pay attention to the terminal console:
/workspaces/git-nuance-demo-merge (main) $ git merge feature
Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
Automatic merge failed; fix conflicts and then commit the result.
Check this out: "fix conflicts and then commit the result"
It instructs us to both fix and commit so that the conflicts can be resolved. This means a new commit is needed when resolving merge conflicts.
Resolve it by selecting "Accept Incoming Change", which in this case means accepting the changes from the feature
branch.
If you check the log by running git log main
, you'll notice the new merge commit in the logs saying "Merge branch...":
/workspaces/git-nuance-demo-merge (main) $ git log main
commit 4d6ac5eb811be1a6130c6bbcde215946c618dd95 (HEAD -> main)
Merge: ac466be 58beb1e
Author: Kev <kev@test.com>
Merge branch 'feature'
commit 58beb1e4f1d47f9bf1364be906a2e22b5280be1d (feature)
Author: Kev <kev@test.com>
D
commit ac466bea2d13762608660e27798ba08b840529db
Author: Dash <dash@test.com>
C
commit fd6ecc11625c41c06e150015254b16a620c1cd44
Author: Leader <leader@test.com>
B
...
Wondering what the change is in this new commit?
Run git diff 4d6ac5e~ 4d6ac5e
(4d6ac5e is the commit ID (hash))
/workspaces/git-nuance-demo-merge (main) $ git diff 4d6ac5e~ 4d6ac5e
diff --git a/example.txt b/example.txt
index 22ad65d..d49a266 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,2 @@
Initial content
-Modified by Dash
+Modified by Kev
If you take a deeper look at the details of this new commit, pay special attention to the line:
Merge: ac466be
58beb1e
What does it mean?
This merge commit has two parents, pointing to commit C
and commit D
:
Graph After Merge
After resolving the conflict, the commit graph looks like:
A---B---C---M [main]
\ /
D [feature]
Option 2: Rebase feature
onto main
Before continuing with the rebase process, take a look at the feature
branch's logs at this point:
/workspaces/git-nuance-demo-merge (main) $ git log feature
commit 58beb1e4f1d47f9bf1364be906a2e22b5280be1d (Head -> feature)
Author: Kev <kev@test.com>
D
...
The commit D
's ID (hash) is 58beb1e....
The change in this commit is:
/workspaces/git-nuance-demo-merge (main) $ git diff 58beb1e~ 58beb1e
diff --git a/example.txt b/example.txt
index a53a0e2..d49a266 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,2 @@
Initial content
-Added by leader
+Modified by Kev
The commit ID and changes will be altered after the rebase; we'll see why.
Rebase
Let's now start the rebase process.
Alternatively to option 1, rebasing involves integrating the changes from main
directly into the feature
branch history.
cd git-nuance-demo-rebase
git checkout feature
git rebase main
Rebase Conflict
It creates a merge conflict that is almost identical to the one in option 1:
Initial content
<<<<<<< HEAD
Modified by Dash
=======
Modified by Kev
>>>>>>> 58beb1e (D)
But what's in the terminal console is different:
/workspaces/git-nuance-demo-rebase (feature) $ git rebase main
Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
error: could not apply 58beb1e... D
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 58beb1e... D
It instructs us to "Resolve all conflicts manually", and continue with git rebase --continue
.
Alright, let's follow the hint to resolve the conflict (also by selecting "Accept Incoming Change").
git add example.txt && \
git rebase --continue
Take a look at the content after running git rebase --continue
:
D
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto 05cfdd2
# Last command done (1 command done):
# pick 58beb1e D
# No commands remaining.
# You are currently rebasing branch 'feature' on '05cfdd2'.
#
# Changes to be committed:
# modified: example.txt
#
Git offers us an editor to edit the commit message of D
.
You can rewrite commit D
by editing the message (lines starting with "#" will be ignored).
I made no changes and just closed the commit editor, so the rebase process proceeded with the rewriting of commit D
.
Now that the rebase process has finished, take a look at the commit history of the feature
branch:
/workspaces/git-nuance-demo-rebase (feature) $ git log feature
commit 047c3480da20d9dd6c530d3698b4e047f1f1d9d9 (HEAD -> feature)
Author: Kev <kev@test.com>
D
commit ac466bea2d13762608660e27798ba08b840529db (main)
Author: Dash <dash@test.com>
C
...
Pay special attention to the commit ID of the new D
.
It's now 047c348...
. Remember what it was before the rebase? It was 58beb1e...
.
And the change in this commit is:
/workspaces/git-nuance-demo-rebase (feature) $ git diff 047c348~ 047c348
diff --git a/example.txt b/example.txt
index 22ad65d..d49a266 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,2 @@
Initial content
-Modified by Dash
+Modified by Kev
See? The change is also different from the previous commit D
because the commit D
was rewritten in the rebase process.
Graph After Rebase
Rebase modifies the original commit D
on the feature branch:
A---B---C [main]
\
D' [feature]
In this case, we performed a history rewrite, causing the original commit D
to disappear and the new commit D'
to emerge.
Recap
Commit History
Now let's recap the primary steps and Git histories in both options we covered.
Option 1: Merge | Option 2: Rebase | ||||||||||||||||||||||||||||||||||||
|
|
You can also view the histories in my demo repositories:
Commit Graph of Taking Each Option
- Merge
A---B---C---M [main]
\ /
D [feature]
- Rebase
A---B---C [main]
\
D' [feature]
The obvious difference is that there are five commits (A-B-C-D-M
) after a merge, and four (A-B-C-D'
) after a rebase (D disappears because it is rewritten).
Git Rebase can be Dangerous
Merge preserves the exact historical sequence of changes and is generally easier for beginners to understand and handle, especially in a collaborative environment.
In contrast, Rebase rewrites the commit history to make it appear as if your changes were created on top of the latest remote commits.
This can result in a cleaner, more linear commit history but can be confusing because it alters the commit history. This might be trickier in a collaborative project unless all contributors are comfortable with Git.
This is typical evidence of why Git rebase can be dangerous:
In more complex, real-world scenarios, there is a risk that you could rewrite, overwrite, or delete another developer's commit from the history, preventing your teammates from finding their previously made commits.
There are some basic principles in using rebase like "Don’t rebase a branch that’s been published remotely" and "Create a backup branch from the tip of the branch you’re about to rebase" etc.
There are some basic principles for using rebase, such as "Don’t rebase a branch that’s been published remotely" and "Create a backup branch from the tip of the branch you’re about to rebase".
Choosing between merge and rebase based on your project's needs is a broad topic that is not comprehensively covered within the scope of this post. For more detailed information, I recommend reading "Differences Between Git Merge and Rebase — and Why You Should Care".
Key Takeaways: Merge or Rebase?
Both strategies have their merits:
Merge
: You create a new commit that shows how conflicts were resolved, allowing your teammates to clearly see the resolution process.Rebase
: You modify existing commits. When resolving conflicts during a rebase, you may alter code or commit messages that others have previously made. This can disrupt collaborative work if not used carefully.
Top comments (0)