Merging is at the very core of Git and GitHub. It is the key operation that makes it possible for developers to share their work with their teammates and collaborate with one another remotely. More than one method of merging exists, and they each have their own strengths to consider. The three-way merge is largely an improvement on the two-way merge, but both can come in handy if you know when to use them.
What is a 2-way merge?
A two-way merge is actually a standard applied diff by all objective measures. A diff operation yields a record of the changes that have taken place in your repository between two distinct reference points. The reference points in question can be any of the following pairs:
- The working tree and the current index
- The working tree and another tree
- The index and a tree
- Two distinct trees
- Pre-merge and post-merge changes
- Changes between two blob objects
- Changes between two files
To shed some light on the pairs mentioned above, here is a bit of helpful information about some key terms:
- A blob is actually a combination of the raw binary data contained within a given file and metadata denoting its size as well as its type (which is always "blob"). Blobs are also referred to as byte (or octet) streams and are automatically created by Git whenever you add a file to your staging area.
- A tree is an object that matches all generated blobs with their respective paths and names. So-called "cache entries" are used to store essential file information within a tree. These describe the file's permissions, path, name, and the SHA-1 hash of its associated blob.
- The index is a list of all of the resources needed to create the full tree of directories and files that are used to form your next commit. It stores the entire state of Git's HEAD pointer, plus all of the changes that have been made. When you actually proceed with your commit operation, the index effectively provides everything required to generate the commit tree.
By completing a "git apply" operation using a diff or patch file, changes from a remote codebase can be applied to a local one. This approach to merging is used when people bring other people's code into their own local repositories directly. Performing such an operation is known as applying a patch or applying a diff and has retrospectively been described as a "two-way merge". However, the new name often serves only to confuse those new to the process as an actual merge does not take place this way at all.
If you already have a bit of experience with Git but this strategy still seems foreign to you, don't worry—it's actually a fairly old (and often outdated) technique. In fact, applying diffs has largely been replaced with the use of pull requests at this point. Developers would create patch files containing all of the Git commit metadata and code needed to recreate the full tree of changes they had personally completed, then send the files to other members of their team. Once received, the files could then be applied to each team member's local repository, adding changes made by others directly and, thus, synchronizing contributors' codebases. Pull requests represent a major departure from the two-way merge in which a three-way merge takes place instead.
What is a 3-way merge?
At its core, a three-way merge is technically any kind of merge involving two divergences from a merge base.
In other words, whenever a three-way merge is performed, the common ancestor of two distinct versions of a file or codebase is used as the base of the merge operation itself. This base serves as a reference upon which more complex logic can be performed. The logic that leverages the merge base to perform such a merge is tasked with determining whether either of the two separate code versions taken into consideration differ in ways that are irreconcilable. If they do, a merge conflict (or multiple merge conflicts) is logged, preventing the merge from being completed until the issue has been solved.
A merge commit on GitHub often uses the fast-forward strategy to simply move the HEAD pointer up to the latest change. However, this is not always possible. When there are new changes that have taken place since a given feature branch was created, a three-way merge becomes a necessity. This way, the common ancestor of the two diverging branches can be used to guide the merge operation. Once completed, a merge commit is added in which the diverging branches are reunited once more.
Make merging much easier
Merging does not need to be difficult or tedious to handle, but GitHub's default settings can make it feel that way. With Mergify, you can initiate merges the minute your CI pipeline processes each pull request—no manual intervention necessary. Queue up merges, automate onboarding and more with Mergify. Request a demo today to get started.