Thoughts and Notes on Software Development

Trying to Understand Revert And Merges in Git

Disclaimer: I'm not an expert in Git. This post is me trying to understand how it works, by trying to explain it to someone else. If I've said something wrong in this post, please don't hesitate to correct me by leaving a comment below or getting in touch with me.

So, in the past, I got confused by how a git revert command affected the merges. Part of it is due to my TFS background. But anyway, as it turns out, it works exactly how it is supposed to work, once you have a better understanding of how Git works.

A git revert rolls back a specific commit made in the repo. In addition to that, it adds a new commit to indicate that the previous commit was rolled back.

To better explain how this affects merges, I'll try to go over a scenario where I merge branches, then revert a commit in one branch, then try to merge the branches again. So here goes.

Imagine that I have two branches, develop and master. The develop branch was created based off the master branch, so they should have the same code in them.

Let's say that in the develop branch, there were new commits added. Let's call them Commit 1A, Commit 1B and Commit 1C.

Then I decided to merge develop into master.

After the merge, both branches are in sync again. If you look at the history of commits into both branches, at the top of the list would be Commit 1C, then Commit 1B then Commit 1A.

Depending on how you do your merges, the master branch would normally have an extra commit showing the merge from the develop branch. Let's call that Commit 2A. But after that commit though, you would still see Commit 1C, then Commit 1B then Commit 1A, just like in the develop branch.

Anyway the point is, at this moment in time, both branches have the same set of changes/commits. Everything that is in the develop branch, is also in the master branch.

Table below shows the history of commits for both branches.

develop master
Commit 1C Commit 2A
Commit 1B Commit 1C
Commit 1A Commit 1B
... Commit 1A

But then I decided to revert a change in the master branch because it broke something. Let's say I reverted Commit 1B in the master branch. Since a git revert command creates a new commit, it will show up in the history for the master branch too. Let's call this Commit 2B.

Around the same time, I also pushed new code to the develop branch. Let's say that was Commit 1D.

The updated table below shows the commits for both branches at this point in time.

develop master
Commit 1D Commit 2B
Commit 1C Commit 2A
Commit 1B Commit 1C
Commit 1A Commit 1B
... Commit 1A

And so what happens now when I merge the develop branch into the master branch?

Previously, I expected that if I merged develop into master at this point in time:

  1. I would get Commit 1D added to the master branch and...
  2. I would get Commit 1B added back to the master branch...

But that is not how Git works. At least not for the 2nd expectation. That may be the case with a merge in TFS, but not in Git. And that's because of the effect of the git revert command and how Git compares differences between branches during a merge.

The git revert command creates a new commit to track the rolling back of a previous commit. In this case, it created Commit 2B. When you merge develop into the master branch at this point, Git will look at the commit history in the develop branch and will say, “Hey, the master branch has all of the same commits except Commit 1D, so I'll only add Commit 1D into the master branch.”

So what about Commit 1B that was reverted in the master branch, but is still in the develop branch? Why was that not merged?

Since merges are directional (source branch to target branch), Git sees that Commit 1B in the develop branch (source branch) is already in the history of commits for the master branch (target branch). So as far as a merge goes, Commit 1B is already in the master branch and so there is no need to add it during a merge.

Now if you were to reverse the direction of the merge, if you were to merge the master branch into the develop branch, Git will now see that Commit 1B was reverted in the master branch (source branch) via Commit 2B. And so Git will then revert Commit 1B in the develop branch (target branch) by copying the changes from Commit 2B as part of the merge.

So, there you go. I hope this post can help someone else who had trouble understanding how reverts and merges work in Git.

Tags: #Git

Useful References:

Discuss... or leave a comment below.