Skip to main content

Three-way merging

Merging is the process of unifying changes that have been made to two copies of (usually) the same file back to a single source of truth. In the context of a version control system, we can consider each copy to be a different version of the file. Instead of using sequential version numbers, GIT tracks these versions using commit hashes.

What is a merge conflict?

A merge conflict occurs when we try to combine edits made to the same lines of text in two copies of the same file and it is impossible to determine automatically which file copy contains the correct line.

What is a three-way merge?

To assist in resolving such a conflict we can look to three files. The original file at the time the two copies were made (common ancestor or base) as well as the two copies of the original that was edited.

It follows then that to resolve the merge conflict we will be able to inspect these three files to then determine a correct line. This could mean picking a line from one of the three files or even writing a completely new line based on the information available.

In any case, we are merging the knowledge gotten from three files to resolve the conflict, which is why this process is also called a three-way merge.

Advantage of a three-way merge

While we could just inspect the two file copies which are giving us trouble (that would be a 2-way merge) having that third file which is the common ancestor of our two copies provides us with extra information. Most importantly this third file affords us (our merge tool) to compare the changes made to each copy with the original file that allows us to determine if a line in one of the copies was added or removed. This would not be possible if we just compared the two copies to each other, all we would see is that there is a difference but not if a line was added or removed.

Knowing your left from your right

When resolving merge conflicts in GIT instead of “Copy A/Copy B”, GIT uses the terminology “local/“remote” or “ours/theirs” which unfortunately can be somewhat unintuitive at times.

To understand the terminology let's recall that a merge operation “merges” the changes made in one branch into another. Alternatively, if we think of a merge operation as taking a bunch of stuff from A and stuffing it into B. Then A is always called (remote/theirs) and B is always (local/our).

As we will see shortly when merging and rebasing “our” code changes can sometimes be called (local/ours) and at other times (remote/theirs).

The use of these words can cause great confusion when resolving merge conflicts as it can be unclear if our code is the one in point B (local/ours) or on A (remote/theirs) side.

Typically, when we explicitly merge a branch, we will first check out the branch which will receive the changes. This is the target branch A, in GIT terms (local/ours). We will then run the GIT MERGE command specifying the B branch that we want to merge into the target. This is what GIT calls the (remote/theirs).

Where the terminology gets confusing is when using GIT REBASE.

When issuing a rebase command our currently checked out branch becomes A as it is the source branch and the other branch is designated as B (we will be adding our commits on top of that branch).

Now, if we apply the GIT terminology the source of confusion will become clear as now our branch in GIT terminology is referred to as (remote/theirs) and the branch we are rebasing onto is called very confusingly (local/ours).