Skip to main content
Two-way and three-way merges explained

A short explanation on the difference between two-way and three-way merges in git.

Patrick Boyd avatar
Written by Patrick Boyd
Updated over 7 months ago

When two branches are merged in git, it will attempt to combine any changes that have happened on those two branches into a set of resolved files. There are two ways that git can choose to merge a file, a two-way or a three-way merge.

Which strategy git used has been important in our merge conflict resolution at the front end, as up until recently, we were only able to support individual conflict resolution for files that had been merged via a three-way merge.

The following examples will explain the main differences between a two-way and three-way merge.

Three way merges

When git has an earlier commit for a file that was common on the two branches that are being merged it will perform a ‘three way merge’.

The diagram above shows a simple git branching strategy with some changes on two feature branches. It shows what would happen if we had taken the following steps:

1) A file HelloWorld.cls is added to the main branch in commit A (probably after merging a PR down after testing in UAT). This file has the following content:

public class HelloWorld { 
public static void sayHello() { System.debug('Hello, World!'); }
}

2) A user then changes that file on branch Feature1 in commit B, amending the class name from HelloWorld to HelloMundo.

public class HelloMundo { 
public static void sayHello() { System.debug('Hola, World!'); }
}

3) Another user also makes changes to the same file on branch Feature2 in commit C, changing HelloWorld to HelloMonde.

public class HelloMonde { 
public static void sayHello() { System.debug('Bonjour, World!');}
}

4) Feature1 branch is merged down to the UAT branch without any conflicts.

5) However, when Feature 2 branch is merged, git will find conflicts to deal with, as the file has changed on the same lines in two different commits since the last commit in the history that is common to these two branches (commit A in this case).

In this case, the file will be merged via a 3 way merge in git, using the following inputs:

  • Merge base: the version from commit A

  • Source: the version from commit C

  • Target: the version from commit B

Two way merges

A two way merge happens when git does not have a commit in the history that is common to the file that changes.

The diagram above shows a very similar situation to what was described above, with the difference that the initial commit on main does not contain a copy of the file HelloWorld.cls.

1) A user adds a new file called HelloWorld.cls on branch Feature1 in commit A

public class HelloMundo { 
public static void sayHello() { System.debug('Hola, World!'); }
}

2) Another user also adds a file with the same name, HelloWorld.cls, on branch Feature2 in commit B, but with different content.

public class HelloMonde { 
public static void sayHello() { System.debug('Bonjour, World!');}
}

3) Feature1 branch is merged down to the UAT branch without any conflicts.

4) However, when Feature2 is merged, we will have conflicts to deal with as we have a file that has been 'added' on both sides of our merge and that file has differing contents.

In this case, git is not able to find a common 'merge base' in the history, as this file has been added to both sides of the merge since those branches split. In this case, git will merge them via a two way merge:

  • Source: the version from commit B

  • Target: the version from commit A

Why does this matter in Gearset?

The initial merge conflict resolution feature relied on files being merged using a 'three-way' merge strategy. This allowed the application to use the extra context of being able to see how the file changed from the base on both branches.

Gearset now supports individual conflict resolution for two-way merges as well.

Did this answer your question?