Theodo apps

Never Fear Git Conflicts Again: Smart Tips for Smooth Merges

I’ve finally finished my task, the pull request has been reviewed, all comments are resolved, and I’m about to merge it… when suddenly Github prompts you to fix conflicts. This is a frustrating experience for any developer. Why do conflicts happen anyway? Is it simply because two developers changed the same line? How can I resolve conflicts quickly? How can I avoid having conflicts in the first place? Let’s discover git conflicts and learn some tips along the way.

I - What is a Git Conflict?

The common understanding among developers is that "a Git conflict happens when two developers change the same line of code in parallel." However, this is a simplification. In reality, a Git conflict occurs when Git cannot apply the diffs it computed. This happens because the current code (the version the diffs were based on) has changed in two incompatible ways.

Git creates diffs by defining “hunks”, which are essentially chunks of code lines that are different between the “current” and “incoming” code. Hunks are calculated using the gnu diff tool, which is designed to compare two files or two versions of a file. Several algorithms can be used to produce this result. Given its low time complexity the default is the Myers Difference algorithm. Other options include:

  • minimal: Produces the smallest number of hunks but takes more time. However, improvements might only be noticeable on very large files.
  • patience: Used for generating patches, it employs a different approach that relies on matching important lines within the context of code files before using Myers for the rest.
  • histogram: This algorithm follows a similar approach to patience but aims to continue finding diffs in a human-readable way. This is considered the best algorithm for readable diffs, and therefore the best for resolving conflicts, more details about this further down 😉

Given that Git breaks a file into smaller batches — the hunks — conflicts arise when two developers modify the same hunk in different ways. This explains why conflicts can arise even when developers change two successive but different lines (like those pesky import conflicts).

Two distinct lines were changed but it still causes a conflict

II - How to Decrease Conflict Occurrence

Several strategies can help reduce conflicts in a team project. These practices are recommended not only for minimizing conflicts but also for several other benefits:

Small Pull Requests

Small pull requests lead to smaller conflicts that can be fixed more quickly. They also prevent conflicts from accumulating over numerous commits (a useful tip for automatically resolving this scenario later).

Small PRs also allow for efficient distribution of work among the team and enable continuous delivery of value.

Standard Formatting/Linting

Using standard formatting and linting rules eliminates unnecessary conflicts related to code style.

Define Scopes Carefully for Parallel Work

When planning the technical design of a feature, break down tasks in a way that creates distinct file scopes for each developer. The fewer developers working on the same files, the rarer the conflicts will be.

Rebase Often

Rebase as frequently as possible to catch any conflicts early and make the process of solving them much simpler.

III - How to Solve Conflicts

When resolving conflicts, you have three options:

1. Keep current — in a merge, this is the code from your branch (the one you started the merge from). In a rebase, this is the code from the base branch you’re rebasing onto.
2. Keep incoming — in a merge, this is the code from the branch you’re merging into (often main or master). In a rebase, this is the code from your branch (the one being rebased).
3. Combine both — manually merge the relevant parts of each version into one.

Here are some tips to make the process of choosing the right option amongst the three easier:

Ignore Whitespaces

A quick but effective tip: you can add the --ignore-whitespace flag to a git rebase command to skip whitespace-only changes. Since whitespace formatting should be handled by your formatter, ignoring them can greatly reduce conflict noise.

That said, don’t forget to format your code afterward. I recommend using this flag only when rebasing branches with heavy conflicts, and then running a lint check to ensure everything is clean. It’s also best practice to enforce lint checks at the CI level—something most teams already do today.

Diff Strategy

As mentioned earlier, choosing the “Histogram” diffing strategy offers the benefit of producing the most readable diffs, which facilitates easier conflict resolution sessions.

Enable this option using the command: git config --global diff.algorithm histogram

The only scenario where it might be useful to use a different preset is when generating large diffs or patches using Git. In such cases, use the --diff-algorithm=minimal flag for specific commands.

here are two versions of a file, before and after a change:

File before change
Before change
File after change
After change

and here are the diffs generated by Myers vs Histogram algorithms:

File diff with Myers algorithm
Myers algorithm
File diff with Histogram algorithm
Histogram algorithm

We can see that the histogram algorithm tends to group bigger chunks which leads to more comprehensible diffing.

Accept all incoming or current code

When you are certain which of the code you would like to keep, yours or master’s, you can use the flags --X theirs / --X ours to automatically accept all incoming, or all current code.
But which one is which ? it can be tricky, and people have different ways of memorizing this, but here’s my way : whether you’re rebasing your branch over master, or merging your branch into master, ours represents the code over at master, and theirs represents the code of your branch. If you need to merge or rebase branches A and B, just think of the base one as master and apply the same logic.

Yes it’s confusing, if you want to understand why it is this way, or if you want to know which option to choose in more complex scenarios than rebasing/merging a branch onto another, this Stack Overflow comment has been quite helpful for me.

You can use the --X flag to apply the same strategy for all conflicts over a rebase or merge, but you can also use git checkout . --ours / --theirs directly at a single commit level to accept all incoming/current code for all the conflicts pertaining to that commit. (Still with the same logic : ours / current code is master’s code,  theirs / incoming code is your given branch’s code).

Avoid repeating the same conflict resolutions

rerere (reuse recorded resolution) is a hidden git option which, as its name suggests, allows to reuse recorded resolutions in the context of conflicts.

That means that when you resolve a given conflict, it will be recorded by git, and when a similar conflict happens, git automatically solves it the same way you did. This is particularly helpful when merging a long standing branches where the same conflict can happen several times.

But can I trust git’s dark magic ? what if for once I don’t want to resolve this conflict the same way ? well no worries, git only resolves the conflict for you, but it does not stage it nor commit it, this way you can always review the resolution before approving it.

Finally, git stores the recorded resolutions in a .git/rr-cache directory, so if you ever want to reset your cache all you have to do is delete it.

To enable rerere run the command: git config --global rerere.enabled true

Conclusion

Conflicts are an unavoidable part of working with Git, but with the right mindset and tools, they don’t have to be a pain. By keeping pull requests small, rebasing often, and relying on consistent formatting, you’ll encounter fewer conflicts to begin with. When they do appear, strategies like the histogram diff algorithm, --ours/--theirs, and rerere can save you significant time. And once the merge is done, don’t skip the safety nets: code reviews, type checks, and automated tests are your last line of defense to ensure that resolved conflicts don’t introduce regressions.

In practice, the key is to reduce conflicts where possible, deal with them efficiently when they happen, and rely on tests and reviews to keep things safe.

Resources

Développeur mobile ?

Rejoins nos équipes