Advanced Git merge conflict resolution techniques
Advanced Git Merge Conflict Resolution Techniques
To resolve a Git merge conflict safely, first update your branch with the latest target branch, inspect the unmerged files, understand the base/current/incoming versions, resolve each conflict, stage the result, test it, then complete the merge and push the updated branch.
For harder conflicts, Git provides tools that make that process easier: zdiff3 conflict markers show the common ancestor, git mergetool can launch a visual three-way merge tool, rerere can reuse previous conflict resolutions, and merge strategy options can help with specific cases such as whitespace-heavy changes.
This article focuses on merge conflicts. If you are deciding whether to integrate changes with merge, rebase or cherry-pick in the first place, see our companion guide: Git Rebase vs Cherry-Pick: Alternatives to Git Merge.
Key Takeaways
- Resolve the conflict based on the intended final code—not simply by choosing “ours” or “theirs”.
- Use
git statusand Git’s three-stage conflict information to understand what is actually unresolved. merge.conflictStyle=zdiff3can make difficult conflicts much easier to understand by showing the common ancestor.- Use
git mergetoolwhen a visual three-way comparison is easier than editing conflict markers manually. - Enable
rererewhen your workflow repeatedly encounters the same conflicts. - For cross-platform teams, commit line-ending rules in
.gitattributesinstead of relying only on each developer’s global Git configuration.
What Is a Git Merge Conflict?
A merge conflict occurs when Git cannot determine a safe automatic result while combining development histories.
Common causes include:
- two branches changing overlapping lines differently;
- one branch modifying a file that another branch deleted;
- renames or directory changes that overlap with other edits;
- large branch divergence that causes otherwise independent work to overlap;
- line-ending, indentation or formatting changes that make a file appear more different than it really is.
A conflict does not necessarily mean either branch is wrong.
It means Git needs a human—or a deliberately configured rule—to decide what the final content should be.
A Reliable Merge Conflict Resolution Workflow
Suppose you have a feature branch with an open pull request into main, but main has changed and the pull request now conflicts.
A clear local workflow is:
git switch my-feature-branch
git fetch origin
git merge origin/main
If Git reports conflicts, start with:
git status
Git will list the unmerged paths that still need attention.
Resolve each file, then stage it:
git add path/to/resolved-file
Once all conflicts are resolved, run your relevant tests and checks, then complete the merge:
git commit
Finally, push the updated feature branch:
git push origin my-feature-branch
That updates the existing pull request with the conflict resolution.
If you realise the merge is going in the wrong direction or you want to return to the state before it began:
git merge --abort
Bitbucket Cloud currently requires conflicting pull requests to be resolved in a local repository and pushed back to the source branch. Bitbucket Data Center likewise directs users to resolve conflicting merge/rebase operations locally before retrying the pull-request merge.
Understand the Conflict Before Editing It
Git’s standard conflict markers look like this:
<<<<<<< HEAD
code from the current branch
=======
code from the branch being merged
>>>>>>> origin/main
The goal is not merely to remove the markers.
You need to produce the code that should exist after both lines of development are understood together.
For a normal two-branch merge, Git internally keeps up to three versions of each conflicted file:
- stage 1 — base: the common ancestor;
- stage 2 — ours: the version from the branch you were on when you started the merge;
- stage 3 — theirs: the version from the branch being merged in.
You can inspect those versions directly when a conflict is difficult to understand:
git show :1:path/to/file # common ancestor
git show :2:path/to/file # ours
git show :3:path/to/file # theirs
This is often more useful than comparing only the two final sides because it shows what each branch changed relative to the same starting point.
Use zdiff3 to Make Conflict Markers More Informative
One of the most useful improvements for manual conflict resolution is Git’s zdiff3 conflict style.
Enable it globally with:
git config --global merge.conflictStyle zdiff3
Instead of showing only “ours” and “theirs”, zdiff3 also includes the common ancestor:
<<<<<<< HEAD
our changed version
||||||| base
original version before either branch changed it
=======
their changed version
>>>>>>> origin/main
That extra base section can make the intent of both changes much easier to reconstruct.
Git also supports diff3. zdiff3 is similar, but removes matching lines near the beginning or end of the conflict region so the markers are often more compact.
Use “Ours” and “Theirs” Carefully
For a normal merge, if you know that the complete file from one side is the correct result, Git can restore that side:
git restore --ours path/to/file
git restore --theirs path/to/file
The older equivalents still work:
git checkout --ours path/to/file
git checkout --theirs path/to/file
Then stage the chosen result:
git add path/to/file
These commands are useful, but they are intentionally blunt: choosing --ours for an entire file discards the conflicting file content from the other side, and vice versa.
Do not use them as a shortcut when the correct result actually requires combining both changes.
Also note that ours and theirs can be counter-intuitive during a rebase, because Git is replaying commits onto another base. This section applies specifically to an ordinary merge workflow.
Use a Visual Merge Tool for Complex Conflicts
For large or complicated conflicts, a three-way merge interface is often easier to reason about than raw markers.
Git’s git mergetool command is a launcher for a configured merge utility:
git mergetool
Git can work with tools such as KDiff3, Meld, vimdiff and other supported utilities, and IDEs such as VS Code and JetBrains products also provide visual conflict-resolution interfaces.
A useful three-way view normally gives you:
- the common base;
- your branch;
- the incoming branch;
- the final merged result you are building.
The visual interface does not make the decision for you. Its value is making the relationship between the competing changes easier to see.
Use Git Diff and History to Understand Why a Conflict Exists
git diff is useful while a conflict is unresolved because Git can show the unmerged state rather than just an ordinary before/after diff.
You can also compare the working result with individual conflict stages:
git diff --base path/to/file
git diff --ours path/to/file
git diff --theirs path/to/file
If the reason for a change is unclear, history can provide context:
git log -p -- path/to/file
git blame path/to/file
git blame is most useful as a route to the commit that introduced a line—not as a way to decide which developer is “responsible” for the conflict.
Open the relevant commit and understand why the code changed before choosing a resolution.
Whitespace-Heavy Conflicts
Sometimes the apparent conflict is dominated by formatting or whitespace rather than a substantive code disagreement.
Git’s default ort merge strategy supports options such as:
git merge -Xignore-space-change origin/main
git merge -Xignore-all-space origin/main
ignore-space-change treats changes in the amount of whitespace as unchanged for the three-way merge.
ignore-all-space is more aggressive and ignores whitespace when comparing lines.
These options should be used deliberately. They do not mean “throw away every whitespace-containing line”, and substantive changes mixed with formatting changes still need to be understood.
If formatting-only conflicts happen regularly, preventing those diffs is generally better than repeatedly resolving them later.
Do Not Confuse -Xours With the “ours” Merge Strategy
This distinction catches experienced Git users too.
With the normal merge strategy:
git merge -Xours other-branch
-Xours tells Git to favour your side only for conflicting hunks. Non-conflicting changes from the other branch are still merged.
This is very different from:
git merge -s ours other-branch
The ours merge strategy records the other branch as merged while using the current branch’s tree as the entire result. It effectively ignores the other branch’s content.
That behaviour has specialised uses, but it is not a general-purpose conflict-resolution shortcut.
Use rerere When the Same Conflict Keeps Coming Back
If you repeatedly merge or rebase long-lived branches, you may find yourself resolving the same conflict more than once.
Git’s rerere feature—reuse recorded resolution—is designed for exactly that situation.
Enable it with:
git config --global rerere.enabled true
After you manually resolve a conflict, Git records the before-and-after conflict state.
If Git later encounters an equivalent conflict, it can reuse the previous resolution automatically.
This is particularly useful for:
- long-lived topic branches that are periodically updated;
- rebases that encounter the same conflicts repeatedly;
- maintainers who regularly integrate similar branches.
Always inspect and test a reused resolution.
rerere saves repetitive editing; it does not remove the need to verify that the result still makes sense.
Prevent Avoidable Conflicts With .gitattributes
The original version of this article focused heavily on each developer configuring core.autocrlf.
That setting is still valid, but for a team repository a committed .gitattributes file is usually a better way to define shared line-ending behaviour.
For example:
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
*.jpg -text
This lets the repository define which files are text or binary and which working-tree line endings should be used.
Git generally normalises text to LF in the repository, while the eol attribute can specify LF or CRLF in the working tree where a file type requires it.
Developers can still use core.autocrlf as a local setting, but a source-controlled .gitattributes file means the project’s rules travel with the repository rather than relying on everyone having matching global configuration.
Formatting and Pre-Commit Automation
Consistent formatting prevents a surprising number of unnecessary conflicts.
A team can use tools such as Prettier, language-specific formatters or linters and invoke them through a pre-commit framework or Git hook.
The important distinction is that Git’s pre-commit hook does not format code by itself.
It simply provides a point where your chosen formatter or validation script can run.
Keep formatting-only changes separate from behavioural changes where practical.
Reformatting an entire file while another developer is changing its logic is one of the fastest ways to create a conflict that is difficult to review.
Reduce Merge Conflicts Before They Happen
Not every conflict can or should be avoided, but several habits reduce unnecessary ones:
- keep feature branches reasonably short-lived;
- update long-running branches regularly rather than waiting until the final merge;
- avoid mixing large formatting changes with functional changes;
- define line-ending and text/binary rules in
.gitattributes; - use consistent automated formatting across the team;
- coordinate before several people heavily modify the same area of the codebase;
- keep commits and pull requests focused enough that their intent is clear.
The goal is not zero conflicts. Parallel development will always create legitimate overlaps.
The goal is to remove the accidental conflicts so developer time is spent resolving real design differences rather than whitespace and tooling differences.
Bitbucket Pull Requests and Merge Conflicts
Bitbucket detects when a pull request cannot be merged cleanly, but the conflict still needs to be resolved in Git.
In Bitbucket Cloud, Atlassian’s current guidance is to resolve the conflict in a local repository, commit the resolution and push the source branch. The existing pull request then updates.
Bitbucket Data Center similarly leaves the repository unchanged when a pull-request merge strategy encounters a conflict and directs the user to resolve the operation locally before retrying the merge.
That is another reason to be comfortable with the underlying Git workflow rather than treating merge conflicts as only a Bitbucket UI problem.
A Practical Conflict Resolution Checklist
- Update your local references. Fetch the current target branch.
- Start the merge deliberately. Merge the target branch into the source branch you intend to update.
- Check the unmerged paths. Use
git status. - Understand each conflict. Inspect ours, theirs and—when useful—the common ancestor.
- Use the right tool. Edit manually, use
zdiff3, or launch a mergetool. - Resolve the intended code, not merely the markers.
- Stage resolved files. Use
git add. - Run tests, builds and formatting checks.
- Complete the merge and review the diff.
- Push the source branch so the pull request reflects the resolved history.
Bottom Line
Advanced Git merge conflict resolution is mostly about getting better information before making the final decision.
Use the common ancestor to understand competing edits. Use zdiff3 or a visual mergetool when ordinary markers are too ambiguous. Use rerere when the same conflict repeats.
Use whitespace strategy options only when whitespace is genuinely the problem, and prevent cross-platform noise with repository-level .gitattributes.
Most importantly, remember that a conflict is Git telling you it cannot safely infer the intended result.
The objective is not to make the warning disappear—it is to produce the correct integrated code.
Next: Rebase and Cherry-Pick
Once you are comfortable resolving conflicts, the next question is how you want to integrate development history in the first place.
Continue with Git Rebase vs Cherry-Pick: Alternatives to Git Merge.
By Sean Manwarring, Head of Marketing at Izymes
Published 14 September 2023 · Updated 19 October 2026
Pingback: Git Bisect and Blame: Find Bugs and Regressions Faster | Izymes
October 19, 2026