We can compromise an application by merging problematic code, whether it’s due to accidentally integrating unfinished work into the main branch or overlooking a critical bug that slipped past automated tests.
In this article, I will guide you through the process of using git revert
to safely undo a merge, ensuring that the commit history remains intact and the project’s integrity is preserved.
How git revert
Works
We can think of git revert
as Git’s version of an undo command. However, the git revert
command doesn’t delete commits or jump to a previous state of the branch. Instead, it creates a new commit that reverts the changes from a specific commit.
The syntax to revert a commit with hash <commit_hash>
is:
git revert <commit_hash>
We can list the commits together with their hash identifiers using the git log
command. The output of git log
lists the commits from most recent to oldest commit, like so:
For instance, to revert the commit that implements the subtract function, we would use the command:
git revert 7ba24a3e62d4d37182428ccfaa070baa222b1151
Using git revert
we can undo the changes of a specific commit without affecting the commit history.
Note that git revert
isn’t magical, and depending on the commit history, it can result in a conflict that has to be manually resolved.
Advantages of git revert
Over Manual Changes
Why is git revert
useful if we may need to resolve a conflict manually? Wouldn’t it be easier to just manually undo the changes? Let’s see its advantages:
- Preserves history:
git revert
creates a new commit that undoes the changes of a specified commit while preserving the entire commit history. This helps maintain a transparent history of changes and reversals. - Atomic reversal: It ensures that the reversals are atomic and consistent. When we manually delete and commit changes, there’s a risk of human error.
- Conflict awareness: It ensures that we are alerted through conflicts if there are any integrations or changes dependent on the original commit. This might seem inconvenient, but it safeguards against unintended side effects.
- Metadata: The new commit created by git revert includes metadata and a commit message that contextually describes what was reverted, aiding in future understanding. Without
git revert
, this context might be lost.
Reverting a Merge In Different Scenarios
In this section, we learn how to undo a merge. For the sake of example, we’re assuming we are merging a branch named feature
into the main
branch executing the command from the main
branch:
git merge feature
What we learn here can be applied to any two branches by replacing the names appropriately.
Reverting a merge that has no associated commit
The git merge
command doesn’t always create a new commit. A commit is created only if the main
branch has diverged from the feature
branch. Because git revert
requires a commit to operate one, we can’t use it in this case.
The main
and feature
branches diverge when new commits are created on main
that are not ancestors of the feature
branch. In other words, new commits were created on main
after feature
was created.
If the branches haven’t diverged, when we run the command git merge feature
on the main branch, Git will use fast-forward to merge. This means that it moves the HEAD
of the main
branch to the HEAD
of the feature
branch.
We can observe that this happened by looking at the result of git merge
:
To undo such a merge, we only need to move the HEAD
of the main
branch back to where it was. For this, we:
- Identify the previous
HEAD
using thegit reflog
- Reset the
HEAD
to the previous one usinggit reset --hard <previous_head>
, replacing<previous_head>
with the previousHEAD
.
The output of git reflog
will look something like this:
We can identify the previous HEAD
by looking at the line that says “checkout: moving from feature to main” (it writes feature
and main
because those are the names of our branches).
In this case, the previous head is fe59838
. To move the HEAD
of the main branch back to it and undo the merge, we then use the command:
git reset --hard fe59838
Reverting a merge that has an associated commit
If the main
branch and feature
branch have diverged, then when we merge two branches, a new commit is created, called a merge commit.
The merge commit applies the changes from one branch to another one. In this case, the changes in feature
are applied to the main
branch.
To revert the changes on the main
branch, we use the git revert
on the merge commit. This will create a new commit that undoes the changes brought into the main
branch with the merge, effectively restoring the state of the main branch to what it was before the merge.
First, we need to identify the hash of the merge commit. We can do this using the git log
command:
Because the merge commit has two parents, the syntax of git revert
is slightly different. We need to use the -m 1
option to specify that we want to revert the changes relative to the main
branch:
git revert -m 1 b8dab2c8611e324ed0d273133987415350e6d10d
Conflict Resolution When Reverting a Commit
Sometimes, conflicts may arise when reverting a commit, particularly if the commit being reverted conflicts with later changes in the codebase. In such cases:
- Git will pause the revert: We need to manually resolve conflicts. Git will mark the conflicting files and require intervention.
- Resolve the conflicts: We open each conflicting file, resolve the conflicts marked by Git, and save the changes.
- Stage the resolved files:
git add <file-path>
- Continue the revert:
git revert --continue
Conclusion
Using git revert
to undo merge commits ensures that every change and correction is documented within the commit history.
Additionally, understanding the appropriate scenarios to apply git reset
versus git revert
enables us to make better decisions, especially when considering collaborative workflows or local-only changes.
You can read more about this subject in the FAQs section below. If you want to learn more about Git, I recommend these resources: