Skip to content

Resolving Merge Conflicts


What Will We Learn?

Practically learn How to resolve merge conflicts understanding it from the basics.

Resolving Conflicts

First of all, don’t feel overwhelmed listening resolving merge conflicts. The reason people feel daunting to resolve merge conflicts is conflict markers like this:

<<<<<<< HEAD
HI
=======
HELLO
>>>>>>> feat/auth

Understanding Conflict Markers

When you issue git merge command to integrate changes from target branch to your branch, Git tries to combine those changes but when it found the same line modified differently by both branches, Git can’t continue combining and leave these confilct markers for us to deal with it. So, understanding this structure becomes mandatory.

Let’s look at each line of the file above that has been mark for resolving conflicts.

  • <<<<<<< HEAD: This denotes your branch. You know HEAD will always point to your current branch. So, this is saying:

    The following is the change from this branch

  • HI: And this is exactly what the change from your branch. In your branch, conflicted file has the content HI at that particular line.

  • =======: This is a seperator denoting that changes from your branch ends above this and anything below is the change from incoming branch.

  • HELLO: And this is exact what the change from the incoming branch looks like. The incoming branch has the content HELLO at that particular line.

  • >>>>>>> feat/auth: This suggest the end of change from incoming branch feat/auth.

Generally you keep either top or down of the seperator =======. For example, if you decided to keep the change from incoming branch, you will edit the file and the final content will be HELLO. You remove all the markers and keep only the change you want like:

HELLO

Resolving Merge Conflicts: Practical Way

The Setup

To practically learn how to resolve merge conflicts, we need to set up first so that Git complains to resolve conflicts. For that:

Initialize Git Repo

First of all create new directory and init with Git.

mkdir learn-resolving-merge-conflict
cd learn-resolving-merge-conflict
git init

Write On File

Create file.c and write the following contents:

file.c
#include<stdio.h>
int main(){
    printf("Hello!\n");
}

Initial Commit

Add and commit changes:

git add file.c
git commit -m 'Initial commit'

Commit on New demo Branch

Create a demo branch and switch:

git branch demo
git switch demo

Modify the return type in file.c from int to void:

file.c
#include<stdio.h>
void main(){
    printf("Hello!\n");
}

And commit the change:

git add file.c
git commit -m 'Update return type of main in file.c'

Commit Again on master Branch

Switch back to master branch:

git switch master

And modify file.c. The return type now is float (2.0) and printf is printing Hi!\n:

file.c
#include<stdio.h>
float main(){
    printf("Hi!\n");
    return 2.0;
}

Commit the changes:

git add file.c
git commit -m 'Update return type and return value of main in file.c, also greet Hi'

Merge Conflict

Befre merging, let’s see the commit graph first:

git log --all --graph --oneline --decorate

And it looks like:

* e4300fa (HEAD -> master) Update return type and return value of main in file.c, also greet Hi
| * a83c15d (demo) Update return type of main in file.c
|/  
* 0cf0cff Initial commit

The top two commits shares the same initial parent 0cf0cff. This can be visualized:

    %%{init: { 'gitGraph': { 'mainBranchName': 'master' } } }%%
gitGraph
    commit id: "0cf0cff"
    branch demo
    switch demo
    commit id: "a83c15d"
    switch master
    commit id: "e4300fa"
  

Enough, let’s try to merge the demo branch to our master branch. Make sure you are on master branch.

git merge demo

Git tries to integrate changes from demo to master but fails. And complains:

Auto-merging file.c
CONFLICT (content): Merge conflict in file.c
Automatic merge failed; fix conflicts and then commit the result.

To understand why it fails, let’s see the content of file.c from both branches side by side.

file.c (master)
1	#include<stdio.h>
2	float main(){
3	    printf("Hi!\n");
4	    return 2.0
5	}
file.c (feat)
1	#include<stdio.h>
2	int main(){
3	    printf("Hello!\n");
4	}

Only line no. 1 is the same, all other lines are differnt in each branch. So, git doesn’t know and which change to apply and it doesn’ guess which one to keep. It adds conflict markers.

Resolve Merge Conflict

To resolve, merge conflict, let’s understand the marker place at file.c:

cat file.c
file.c
#include<stdio.h>
<<<<<<< HEAD
float main(){
    printf("Hi!\n");
    return 2.0
=======
void main(){
    printf("Hello!\n");
>>>>>>> demo
}
  • The first line #include<stdio.h> is unchanged. So, Git doesn’t keep it inside markers.
  • The last line } is also excluded by Git because it is extra line in file.c of master which is not in feat. Git doesn’t treat it in conflict boundary.
  • Everything between <<<<<<< HEAD and >>>>>>> demo is conflict marker. And generally you choose any one part seperated by =======.

Generally we resolve conflict in any of the following ways:

Keeping Your Part

If you or your team thinks, your branch code makes more sence, keep your (upper) part:

float main(){
    printf("Hi!\n");
    return 2.0;

Remove all the markers and final code looks like:

#include<stdio.h>
float main(){
    printf("Hi!\n");
    return 2.0;
}

Keeping Incoming Part

If you or your team thinks, incoming branch code makes more sense, keep that (lower) part:

void main(){
    printf("Hello!\n");

Remove all the markers and final code looks like:

#include<stdio.h>
void main(){
    printf("Hello!\n");
}

Combining Both Part

Sometimes, it may make more sense to combine both parts. Here, if you combine both parts and remove markers, it looks like:

#include<stdio.h>
float main(){
    printf("Hi!\n");
    return 2.0;
void main(){
    printf("Hello!\n");
}

If you know C, you know it doesn’t make sense already. So, you can combine parts of both. For example, keep the void return type but print Hi instead of Hello:

#include<stdio.h>
void main(){
    printf("Hi!\n");
}

Important

If you think resolving conflilct is not making sense at all or resolving is complex, you always have the option to --abort the merge process (git merge --abort). This will give you the same state the first time you had started the merge process.

Tip

It is usually helpful to use Code Editors like VS Code to resolve conflict as they provide nice and easy UI for resolving conflicts.

Commit Resolved Changes

The final step in resolving the conflict is committing resolved changes. Add the resolved file.c and commit it.

git add file.c
git commit -m 'Merged demo to master'

The graph after merging:

git log --all --graph --oneline --decorate

Looks like:

*   3834dc7 (HEAD -> master) Merged demo to master
|\  
| * a83c15d (demo) Update return type of main in file.c
* | e4300fa Update return type and return value of main in file.c, also greet Hi
|/  
* 0cf0cff Initial commit

Visually:

    %%{init: { 'gitGraph': { 'mainBranchName': 'master' } } }%%
gitGraph
    commit id: "0cf0cff"
    branch demo
    switch demo
    commit id: "a83c15d"
    switch master
    commit id: "e4300fa"
    merge demo id: "3834dc7"
  
Last updated on