What to do when faced with complex Git Merge tasks - (r)

Jun 23, 2023
Learning more about git merge

Share this on

In the course of work taking place on a development project, Git is an absolute blessing. However, if you have many dancers in the room, some or all of them will step over each other's heels. This means that two developers can be working on the same set of code and both may commit. For these situations you must apply a few Git merger strategies to solve the conflict.

Although an Git merge can be straightforward however, there are usually instances when you'll need an advanced approach. It is when you will need strategies such as recursive merging or three-way merging, as well as several more. You may even need to reverse the Git merge once in a while.

This article will go over some complex Git merging techniques that you can add to your toolbox. Actually, we could begin to dive into the best stuff first!

An Introduction to Git Merge Strategies

The basic concept behind a merge is straightforward it joins two branches in order to make multiple commits one. But, there's several methods you could employ to be sure that you have committed and merged with the correct code.

The Difference Between Two-Way and Three-Way Merges

It is important to know the distinction between a two-way merging and its more three-way counterpart. The majority of merge methods discussed in the next section deal with three-way merges. It's actually easier to define the three-way merger. Consider the following example:

  • You have one main branch that has several commits and an additional feature branch which contains commits.
  • But, if main is now carrying out additional updates, both branches are likely to be divergent.
  • In layperson's terminology in layman's terms, each branch, main and the feature branch have commits the other doesn't. If you merge them with a two-way approach it will result in losing one commit (likely in the case of the main branch..)
  • However, Git will create a new merge commit that is a combination of the currently main as well as feature branch.

In a nutshell, Git will look at three different snapshots and merge changes: the head of main as well as that of feature's head, and the common parent. This is the last commit which is shared by both the main branch and the feature branch.

Fast-Forward Fusion

The first strategy might require you to carry out any action in order to execute. A fast-forward merge shifts the index to the most recent version of the commit the mainwithout adding an extra commit (which may cause confusion.) This is a simple method that many developers use in their standard.

The technique starts by creating a base branch, which may or may not have commits. If this is the case it is possible to open a brand new branch, begin working on the code, and create commits. At this point it is also necessary to bring those changes back into the original. Fast-forward merges have one condition to meet:

  • You need to ensure that no other commits take place on the main branch during the time you are working in your branch.

This won't always be possible particularly if you are as part of a larger team. However, should you opt to combine your commits an existing branch that's in the process of being updated and does not have its own commits, it can be a quick-forward merging. You can do this in two different ways:

git merge 
merging git with --ff-only

Recursive Merging

A recursive merge is often the default, as it will crop up during the most common scenarios than other types of merge. A recursive merger is when commits are made on a branch, but additional commits are made on the main also.

If it's time to merge, Git will recurse over the branch until it can make its definitive commit. This means a merge commit will be able to have two parents when you've completed it.

Like a fast-forward merge it isn't necessary to specify a recursive merge. But, you should ensure that Git isn't choosing something that resembles a fast-forward merge with the following commands and flags:

git merge --no-ff
 
 git merge -s recursive  
 

The second line uses an "-s" strategy option as well as explicit names to perform the merging. Unlike a fast-forward merge, a recursive merge does create a dedicated merge commit. For two-way merges the recursive method is reliable and effective.

Ours and Theirs

An everyday scenario during the development process is when you develop a brand new feature in your project that ultimately won't get the green light. In many cases, you will have a lot of code to merge that's also co-dependent. A 'ours' merge can be the most effective way to deal with the conflicts.

This type of merge can handle as many branches as it needs and avoid all modifications to those branches. This can be a good option for those who want to clean the slate in regards to obsolete options or new features that aren't needed. Here's the command you need:

git merge -s ours  

An ours merge essentially means that the current branch contains the codes de jure. It is akin to "theirs" merges which treat the second branch as the correct. But, you must pass another strategy option here:

git merge -X theirs branch2>

Using ours and theirs merges could be a bit confusing, however it's generally recommended to adhere to common usage scenarios (that is, keeping everything within the branch currently in use and removing the rest).

Octopus

Handling multiple heads - i.e. merging more than one branch into another - can be difficult to handle in a merging git. One could think that you require greater than two fingers to solve the conflict. This is perfect to merge two octopuses.

Octopus merges are what we call and they merge. The typical use case is when you need to add many commits that have similar capabilities and combine the two into one. Here's how you pass it:

git merge -s octopus  
 

However, Git will refuse an octopus merge if you have to make a manual resolution in the future. If resolutions are automatic the octopus merge option will be the default choice in the event that you want to join multiple branches into one.

Resolve

The most secure method to join commits is resolve merges are great if you have a situation which involves criss-cross merges. This is also an easy solution method for implementing. Also, you can employ this technique for more intricate merge histories - however, only the ones with two heads.

git merge -s resolve  
 

Because a resolve merge uses an algorithm that works in three ways to work using both the branch you're currently working on and that you're drawing from, it could not be as flexible like other merging methods. But, for what that you require it to perform, a resolve merge is nearly perfect.

Subtree

This analogy to a recursive merge could confuse you. Let us try to clarify this using a clear example:

  • The first step is to consider two distinct trees: X as well as Y. These are typically two repos.
  • You'd like to join the two trees to create one.
  • If the tree Y is a subtree of one of the subtrees found in X, tree Y is changed to match the pattern of X.
merging git -s in a subtree The branches are: branch1> and branch2>

In short, a subtree merge is what is needed to join two repositories. In fact, you might struggle to understand which merge strategy is right for your. We will then discuss instruments that can assist.

In the beginning, there are several advanced merge conflicts must be resolved. to resolve.

What Can You Do To Handle More complex Git Merge Conflicts

Because conflicts eat the time, cash and even resources, it is essential to figure out how to nip these conflicts in the bud quickly. Most of the time, two developers will work on the same suite of software, and they will both decide to commit.

The result could be that you may not be able to initiate the merge at all due to pending changes or have a failure during the merge which requires manual intervention. When your directory for working is 'clean' you can begin. Lots of times, Git will notify you of any conflicts once you begin a merge:

A Terminal window showing Git commands to show all branches, then merge changes. An error shows as a merge conflict with instructions to fix and commit the results of the conflicts.
A terminal window that shows an unmerged conflict in Git.

For more details on the issue, run git status and see the details:

A Terminal window showing the result of a git status command. It shows a list of files in green, with instructions to resolve unmerged paths.
A Terminal window showing the results of a git status command.

Then, you'll be able to begin working on different files that are causing the issue. Some of the methods and tools we will go over next can help.

Aborting and Resetting the Merges

Sometimes, you have to interrupt the merging process to start with a new beginning. In fact, both of the commands we mention suit situations where you won't yet know what to do when you encounter a problem.

You can choose to abort or reverse a merger that's going on with these commands:

git merge --abort
 
 git reset
 

The two commands are similar and can be used under different scenarios. For example, aborting a merge will simply revert the branch to its status prior to merging. Sometimes the merge won't go as planned. If, for instance, your working directory has no committed changes and not-stashed modifications, you will not be able run an abort.

Reviewing the Conflicts

A majority of merger conflicts are easy to identify and solve. But, in certain instances it is possible to go further to determine the reason a conflict happens and how you can fix it.

It is possible to get a better understanding when you do a merging git with a checkout

git checkout --conflict=diff3 
 

This takes the typical shopping cart's menu and then creates a visual comparison between two separate files which illustrates the conflict between merges:

A code editor that shows the results of running a git checkout --conflict command. It highlights areas of code in red and uses symbols to denote where there’s a change that prevents a commit to take place.
Examining a potential conflict in a specific project document.

Technically speaking in a technical sense, this would check the file over again and remove the conflict markers. This could be repeated several times during the course of resolving. Here, if you succeed in completing through the diff3argument, it will give you the default version as well as alternative versions in the 'ours' and  the 'theirs' version.

The default argument type can be merge and you don't need to set unless you modify the merge conflict's style from the default.

Not noticing Negative Space

Tabs against spaces is a battleground we won't take part in. However, if you have circumstances where formatting is changed from one format to another depending on the file or coding style it is possible to run into the Git merge issue.

You'll know this is why the merge doesn't work since lines will be taken out and new ones added if you analyze the conflict:

A code editor showing the differences between two files with merge conflicts. Each area of difference use symbols to denote the change, along with red highlighting for them.
An image of the conflicts that exist in the editor.

The reason for this is that Git examines these lines and deems negative space as a change.

You can however add specific arguments to the merging command, which will ignore negative space that is present in documents:

git merge -Xignore-all-space
 
 git merge -Xignore-space-change
 

Although these two arguments appear identical, they share distinct differences. If you choose to ignore all negative space, Git will do so. It's a broad brush approach, but in contrast, -Xignore-space-change will only count sequences of one or more negative spaced characters as equivalent. This means that it will disregard single spaces near the ends of lines.

If you want to be extra secure, you could also review the merging process using the no-commit command to verify that you did not miss negative space and count it properly.

Merge Logs

A Terminal window showing the Git log for a repository. There are two commits with yellow titling, along with details on the author, date, and commit message.
Watching and running the Git log from the Terminal.

It's basically a text-file dumping station for every action inside a repo. However, you can include additional arguments in order to improve the look and view only the commits you wish:

git log --oneline --left-right ...
 

This makes use of a 'Triple Dot' to provide an overview of the commits in two branches during the merge. The filter will be able to identify all the commits both branches share, which will leave some commits that you can investigate more thoroughly.

Additionally, you can utilize the git log --oneline option with left-right and --merge to only show commits either on the other side of the merge that "touch" a conflicting file. The -p option shows the exact changes for the specific diff, however, it can only be used for non-merge based commits. This is a solution that we'll discuss later.

Using the Combined Diff Format to Investigate an Git Merge Conflict

You can take the view of Git log deeper to examine conflicting merges. When it comes to normal situations, Git will merge code and then stage the code which succeeds. It will then leave just conflicting lines and you'll be able to see them with the diff command:

A Terminal window that shows the output of a git diff command. It shows details of the changes in green and red text, along with more details of the differences with file names within the repository.
Executing a git diff command in the Terminal.

The 'combined diff' style includes two additional columns of information. The first tells you if there is a difference between your ('ours') branch and the working copy; the second column gives the exact information for branches that are 'theirs.

The signs are a plus sign denotes whether a line is an addition to the original copy, but not in that specific side of the merge, and a minus sign denotes how the line was taken out.

You can see this combined diff format in Git's log by using a couple of commands:

git show
 
 git log --cc -p
 

First, there is a command you use on a merge commit to see its past. This second command makes use of the capabilities of -p to show changes to a non-merge commit alongside the format of the combined diff.

How To Undo a Git Merge

Mistakes can happen There are times when mistakes can occur, and it is possible to carry out merges that require you to retrace your steps on. In certain instances you are able to modify the most recent commit using git commit --amend. It will launch the editor that allows you to edit the last commit message.

While it is possible that you are able to reverse more complex merger conflicts, and consequent modifications, it is difficult because commits are usually forever.

So, there's a lot of steps you should follow

  • First, you need examine commits and locate links to merges which you'll need.
  • Then, check out branches to look over commit histories.
  • When you are familiar with the branches and commits that you need, there are specific Git commands that are based on the intended action.

We'll look at them in more detail and start with reviewing. We can then demonstrate a fast method to undo the effects of a Git merge. We can then examine specific commands to find greater complexity in use.

Review and commit

The git log --oneline command is great when you'd like to look up the revision IDs, as well as the commit messages pertaining to the branch in which you are currently working:

A portion of a Terminal window that shows the output for a one line Git diff command. It shows a minimal number of details: the commit’s hash, branches, and message for each before showing the Terminal prompt.
Executing a single line Git diff command from the Terminal.

The branches of git logged with the* command will show you similar information, but on all branches. Regardless, you can use references IDs along with an checkout in git to create a 'detached HEAD state. That means that you cannot be able to work on any branch from a technical point view, and once you go back to the existing branch, you'll be able to 'lose any changes.

Using git reset

Most of the merge conflicts could happen on a local repo. If this is the case, git reset is the command you need. But, the command comes with more parameters and arguments to explore. Here's how you use the command in practice:

git reset --hard 
 

The initial part of this process - the git reset --hard follows three steps:

  • It relocates the reference branch into its new location prior to the merge commit.
  • The hard reset makes the  index (i.e. the next proposed commit snapshot) look like it is the branch reference.
  • This makes the directory look like the index.

When you execute this method, the history of your commits eliminates the previous commits, and sets it to the referenced ID. It's an easy alternative to undoing the effects of a Git merge but isn't suitable in all situations.

Using git revert

While both git reset and the git reset and git may appear to be identical, they have important differences. In the examples so far you'll notice that the undo procedure is about moving the reference pointers, and the HEAD into an exact commit. It's akin to shuffling around playing cards to create an entirely new set of instructions.

In contrast, git revert generates a fresh commit based on backtracking changes that then modify the reference pointers and makes it the new tip. That's why it's recommended that you use this option to resolve conflicts with remote repo merges.

It is possible to make use of the command git revert to undo the results of a Git merge. It is important to specify a commit reference, or the command won't execute. It is also possible to use HEAD into the command to return to the most recent commit.

You can, however, offer Git greater clarity on what you want to do:

git revert -m 1 

In the event that you use the merge option, the new commit will be able to have two "parents. The first one is related to the name you provide as well as an end of the branch that you wish to merge. If this is the case, -m 1 will instruct Git to preserve the first parent that is - i.e. the specified reference - as the  mainline.'

The default option for git Revert is -e as well as "-edit". The editor will be opened to edit the commit message before you change your mind. But, you may also select no-edit and it won't launch the editor.

It is also possible to pass -n or the option of --no-commit. This instructs git revert to not create a new commit, but instead reverse the changes, and upload these to the staging index and working directory.

The Difference Between Merging and Rebasing into Git

Instead of the git merge command, you can additionally use git rebase. This can also be a means to combine changes into a single directory, with one difference:

  • A three-way merge is the preferred option when you are using git merge. The program combines snapshots of two branches currently in use and then combines the two branches with a common ancestor from both branches in order to make a new commit.
  • Rebasing refers to taking a patched change of a divergent branch, and applying it to a different branch, with no need to find the originator. This means there won't have to be a fresh commit.

For this, checkout to a branch that you'd like to change the base to. From there, you can use this command to do so:

 git rebase -i 
 

Most of the time, your reference will be the main branch. The -i option will start 'interactively refining. It gives users the ability to change the commits that are moved through. You can use this to clean up the commit history. It is one of the big advantages of Git rebase.

When you run the command, it will show a list of potential commits that can be moved into the editor. You have the full power to change how the commit history appears. Additionally, you can combine commits by changing the select command to make them more readable. Once you save your changes, Git will perform the rebase.

In general, you use Git merge to resolve many conflict. Rebasing, however, has a lot of benefits too. As an example, whereas merging is easy to do and allows you to preserve the context of your merge history while rebasing may be more effective since it can make the history of your commits into one.

However, it is important to be extra cautious when rebasing, as the potential for mistakes is massive. Furthermore, don't apply this method to public branches, as rebasing is only going to affect the repo. To correct the problems that arise it is necessary to create even more merges and will have multiple commits.

Tools That Help You manage your Git Merge Improved

The bottom corner of the Intellij IDEA code editor, showing the Git widget panel. The right-click context menu shows, and highlights the Checkout option to review branch changes.
Checking out a branch within Intellij IDEA.

There are additional alternatives with your Command Palette. This is even the case with editors built using VSCode's open source framework, such as Onivim2:

A portion of the Onivim2 screen showing the Command Palette and the Git: Merge Branch command.
Accessing the Git Merge Branch command using on the Command Palette in Onivim2.

As is the case with all of the tools in this list the fact that you don't require the command line in order to execute mergers. The usual procedure is to select the source branch and target branch by using a drop-down menu and then let the editor do the merging. Even so, you don't necessarily have to follow a strict approach. It is possible to look over the modifications afterward, and then create the necessary commit.

The Submline Merge interface, showing a list of commits on the left-hand side of the screen, along with a summary of the changes and conflicts with a specific commit on the right.
It's the Sublime Merge app.

No matter what you pick for a code editor, it often comes with the ability to interact with Git without the command line. It's even the case with Vim and Neovim, using Tim Pope's Git Fugitive plugin. It's extremely user-friendly and straightforward to work with.

However, there are a few dedicated third-party merge software that are focused on the job.

Dedicated Git Merge Apps

For instance, Mergify is an enterprise-level way to merge code that integrates into your continuous integration/continuous delivery (CI/CD) pipeline and workflow:

A section of the Mergify website showing a number of white-outlined graphics on a black background. There are lists of Git branches, timers, code editors, and graphs.
The Mergify site.

A few of the options here help you to automate the updating of your pull request prior to merging, to reorder them according upon priority, and then also batch them. If you are looking for an open-source solution, Meld might be a good choice:

The Meld app interface showing side-by-side code, complete with highlighting in blue and green to denote changes between each file.
The Meld application's interface.

Summary

Git is an indispensable instrument to manage and collaborate code changes efficiently. However, if several developers are working with the same code there could be conflicts. The Git merge strategy can assist in resolving these conflicts and there are many methods to accomplish this. For more complex Git merge methods, you need to turn towards advanced techniques.

The process can be as straightforward as disregarding negative space, or scrolling through the search logs. You don't have to need to utilize the command line either. There are many apps to help you, and your code editor will often use a built-in interface too.

Which one of these Git merger strategies will help get you out of your bind? Please let us know in the comments section below!