This post is part of the series of posts on the Git and Visual Studio where we are discussing in detail on meaning of basic git operations, how to do them in Git and Visual Studio both and understand the difference of both tools. You can find the previous blog post here.
In this blog post, we’ll learn what is merging, types of merge and how to do the same from git command line.
What is Git Merge
Git merging is way of combining the commits made in separate git branches. It is used by git pull command as well to incorporate committed changes from one branch to another branch.
Do note that merging happens to the currently checked out branch or simply current branch. After merge, the current branch will be updated to reflect the merge, but the source branch will remain completely unaffected. Also this means that first we need to checkout the receiving branch and make it a current branch.
How Git Merge Works
When we try to merge two branches in Git, first thing it does it to find last common commit between two branches. Once it does that, it determines the merging algorithm that’s best suited to current merge scenario and finally makes the merge commit. Merge commits are unique against other commits in the fact that they have two parent commits.
If Git encounters a file that is changed in both branches, it will be unable to automatically combine them and therefore needs user intervention, which we would see how to resolve later.
Precautions before merge
Below guidelines are generally required to be done before doing merge:
- Commit your changes in the source branch or the provider branch. Git merge only brings committed changes to receiving branch.
- Checkout the receiving branch. Also use git status to make sure head is correctly pointing to receiving branch. Also make sure there are no uncommitted changes in the receiving branch.
- Get latest from git remote, if any. Execute git fetch followed by git pull or do git pull directly.
A fast-forward merge can occur when there is a linear path from the current branch tip to the target branch. Instead of “actually” merging the branches, all Git has to do to integrate the histories is move (i.e., “fast forward”) the current branch tip up to the target branch tip. This effectively combines the histories, since all of the commits reachable from the target branch are now available through the current one.
For example, moving back to our ConsoleApp02 Project, consider below git log history:
We can see that HEAD is pointing to a branch named newQuickFix branch (so it’s the current branch) and it basically contains two commits bbaad7b and 5bec225. In this case, these two commits are on the top of the master branch commit 42b9d40. So it’s easier for git to just move the HEAD pointer to the latest commit in case of merge.
Perform fast-forward Merge
Continuing above example, let’s say we need to bring changes made to the newQuickFix branch to the master branch. First, let’s make sure that there are no uncommitted changes to current branch newQuickFix:
Checkout the receiving branch (master branch in this case):
Let’s verify that HEAD is now pointing to master branch and there are no uncommitted changes in master branch as well:
Finally, perform the fast forward merge:
Note the output highlighted in above pic. It is clearly mentioning the type of merge used by Git. Now, let’s review git log to check our commit history:
Note that we lost the commit history from the newQuickFix branch and we cannot tell what changes were incorporated from newQuickFix branch.
Track commits incorporated during merge
To keep track of incorporated changes during merge for record keeping purposes, we need to specify that we do not want fast forward merge by using –no-ff flag. In this case, merge is performed using recursive strategy.
For example, reviewing above case, let’s merge change using below command:
If we now see git log history, we can see there is an extra ‘merge’ commit made as 9f75e8f:
Again, we seem to have lost the changes being incorporated. However, that’s not the case. To see that, we need to issue git log command with –graph flag:
In above output, we can clearly see that we brought two commits from the newQuickFix branch.
Most of the time what happens when multiple developers are working on the same project, before you can merge your code to master branch, it has already moved forward containing work of other developers. Note that it’s impossible for Git to perform a fast-forward merge in this scenario.
For example, moving back to our ConsoleApp02 Project, consider below git commit history from master branch:
In this case, we have added a Class07.cs file in the commit 2a262f9 in master branch.
Perform 3-way merge
Again, as specified above, since both of our branches i.e. newQuickFix branch and master branch have moved ahead, there is no way for git to perform fast forward merge. So by default it will merge using recursive strategy only:
Note the git commit history as below:
In above output, we can clearly see that we brought two commits from the newQuickFix branch. Along with that, it also made 2a262f9 a part of merge commit.
What are Merge Conflicts
Occasionally, this process doesn’t go smoothly. If you changed the same part of the same file differently in the two branches you’re merging together, Git won’t be able to merge them cleanly. At this point, Git will no longer perform auto merging.
To reproduce above scenario, we’ll add a file named Class06.cs to master branch and commit our changes. Note from our previous blog posts that we have also modified this file in the newQuickFix branch. Let’s review the commit history of both branches.
From newQuickFix branch:
And from master branch:
Let’s try to merge and observe conflict:
Git hasn’t automatically created a new merge commit. It has paused the process while user needs to resolve the conflict. If we want to see which files are causing issue, we can run git status:
Resolve Merge Conflicts
When Git encounters a conflict during a merge, It will edit the content of the affected files with visual indicators that mark both sides of the conflicted content. These visual markers are: <<<<<<>>>>>>. Its helpful to search a project for these indicators during a merge to find where conflicts need to be resolved.
For example, in our case it would be like this:
Generally the content before the ======= marker is the receiving branch and the part after is the merging branch.
Now knowing this info, we can go in and fix up the merge to our liking. To finish the merge once we are done changing file, all we have to do is run git add on the conflicted file(s) to tell Git they’re resolved. Finally, we can do simple git commit to complete merge:
In next blog post in this series, we’ll learn how to merge commits inside Visual Studio.