git dx

How to Resolve Huge Git Merge Conflicts Without Losing Your Mind

Vikash Patel Jul 17, 2025 7 min read 1379 words

How to Resolve Huge Git Merge Conflicts Without Losing Your Mind

If you’ve ever been knee-deep in a long-lived branch merge and thought, “this can’t be what version control was meant for,” you’re not alone. Git gives us powerful tools, but resolving large merge conflicts, especially during major migrations or rewrites can feel like surgery without anesthesia.

In this post, we’ll walk through how to approach and resolve massive merge conflicts systematically. Not with hand-wavy advice, but with actual steps, real mental models, and clear visual understanding.

Let’s say you’re working on a legacy codebase, maybe something ancient like Django 1.10, and you’re helping move it to Django 4.2. That’s what I had to do once. The problem? Two long-running branches with divergent histories. One branch added features while the other refactored the core framework. Two months of drift meant the merge base was ancient history. When we finally attempted the merge, Git didn’t just report conflicts; it presented a battlefield. Time to toss a coin to your engineer.

Here’s what we saw:


*--*--*--*--*--*-- Feature Branch
 \
  *--*--*--*--*--*--*--*-- Main (Updated Django)

Trying to merge led to a wall of conflicts across dozens of files. Looks like rain. Here’s how to approach this situation methodically.

1. Stop. Don’t Start Fixing Right Away.

Before typing git merge, take a moment. Understand the nature and scope of the divergence.

  • See commits exclusive to each branch:
    • git log main..feature-branch --oneline (commits in feature-branch but not main)
    • git log feature-branch..main --oneline (commits in main but not feature-branch)
  • See the total diff:
    • git diff main...feature-branch (three dots) shows all changes on feature-branch since the common ancestor. This is the work you are trying to merge in.

This preliminary investigation tells you whether you’re dealing with a skirmish or a war. Hmm.

2. Visualize the Conflict

Use:

git log --graph --oneline --all --decorate

Or:

gitk --all

Or use a GUI tool like gitk, Sublime Merge, or your IDE’s Git history viewer. These help you find the merge base: the common ancestor commit from which the two branches diverged.

You can find it on the command line too:

git merge-base main feature-branch

Every conflict is a story about how two different developers modified the same starting point (BASE). Understanding that BASE commit is key.

3. Create a Temporary Merge Playground

Never fight the monster directly on the main branch.

git checkout -b conflict-playground feature-branch
git merge main

Now you’re in a temporary space to resolve without pressure.

4. Know What You’re Resolving

Use a 3-way diff tool. For example, meld, vscode, or sublime-merge diff view.

A 3-way diff tool is non-negotiable. Configure it first:

git config --global merge.tool meld # Or vscode, p4merge, etc.

Then run git mergetool to open the first conflicted file. It will show you three versions:

  • LOCAL: Your version (HEAD, i.e., main).
  • REMOTE: Their version (the one you’re merging in, i.e., feature-branch).
  • BASE: The common ancestor. The state of the file before either branch made changes.

Your job is not to choose between LOCAL and REMOTE. It’s to integrate the intent of both changes into the BASE to create the final, MERGED result.

5. Triage: Divide and Conquer

List all conflicts to see what you’re up against: git status --short | grep "^U" Then, group them into categories:

  1. File-level conflicts: One branch deleted a file that the other modified (DU or UD), or both added the same file (AA). Resolve these first with git rm or git add.
  2. Trivial content conflicts (UU): Whitespace, comments. Easy wins.
  3. Structural conflicts (UU): A class was renamed in main while a method was added to it in the feature. The tool will show a huge conflict, but the resolution is often straightforward: apply the feature’s logic to the newly renamed code.
  4. Logical conflicts (UU): Both branches modified the same piece of logic in incompatible ways. These require the most thought and careful testing.

Tackle them in that order. Steel for humans, silver for monsters and planning for complex merge-conflict. Gaining momentum is half the battle.

6. Use git merge --abort When Overwhelmed

Made a mess? Damn it. No problem.

git merge --abort

Back to a clean slate.

You can retry as many times as needed.

7. Use Commands to Resolve Files Quickly

For some files, you know one side is definitively correct. Don’t open the mergetool; use a command.

  • To accept your version (from main): git checkout --ours path/to/file.js
  • To accept their version (from feature-branch): git checkout --theirs path/to/file.js

This is extremely efficient for structural refactors where you know the main branch’s changes (e.g., a file move or rename) are the correct “canvas” to paint the feature changes onto. After checking out the correct base version, you can manually re-apply the other side’s changes.

For manual resolution, open the file and look for the conflict markers. Edit the code to combine the sections, remove the <<<<<<<, =======, and >>>>>>> markers, and then git add path/to/file.js.

8. Run Tests Aggressively After Each Batch

After every 5–10 files, run your tests. Don’t wait till you’ve resolved 100 conflicts to see what broke. Think of it as taking a potion before the fight, not after you’re already bleeding out.

9. Use git rerere to Avoid Redoing Work

If you abort and retry a merge, rerere (Reuse Recorded Resolution) is your best friend.

git config --global rerere.enabled true

Once enabled, the first time you resolve a hunk of a conflict and git add the file, Git records the “before” (the conflict state) and “after” (your resolution) in the .git/rr-cache directory. If you abort and later encounter the exact same conflict, Git will automatically apply your recorded resolution. This is a massive time-saver. You can even commit the rr-cache directory to share conflict resolutions with your team.

⚠️ Note: rerere works best when the conflicting hunks are identical to the previous conflict. If the context or line numbers shift slightly, Git might not auto-resolve them. It’s useful, but not magic.

10. Distinguish a Strategy from an Option

Sometimes you want to favor one branch’s changes during a merge. There’s a sharp tool for this, but you must know the difference between a strategy and an option.

  • The ours strategy: git merge -s ours feature-branch This is a blunt instrument. It creates a merge commit that includes feature-branch as a parent, but discards all of its changes entirely. The resulting code is identical to main. This is useful for marking a dead-end feature as “merged” to clean up branch history, but it doesn’t integrate any code.

  • The -X ours or -X theirs option: git merge -X ours feature-branch This is far more useful for complex merges. It tells the standard recursive merge strategy to attempt a normal merge, but for any hunk that has a conflict, it automatically resolves it by choosing your side (-X ours) or their side (-X theirs). This is powerful for refactoring merges where you know one side’s changes are globally more important.

11. ASCII Summary: Merging Chaos into Order


Before:
Feature:   A -- B -- C -- D
                  \
Main:              X -- Y -- Z

Conflict explosion: merging D and Z

Resolution Strategy:

  1. Visualize
  2. Playground Branch
  3. Triage by File Type
  4. Resolve in Order (Trivial → Structural → Logical)
  5. Test Often

That old Django migration? It took me three solid days to resolve the merge. I didn’t use any fancy tools, just git mergetool, sublime-merge diff viewers, and manual review. What saved me wasn’t skill; it was method.

Large merge conflicts are not bugs. They’re symptoms of drift. And you don’t fight drift by panic-merging. You fight it with discipline, snapshots, and composure.

Git won’t make it easy. But it gives you enough rope to build a bridge or hang yourself. Your call.

Resources Worth Reading

  1. Git Branching - Basic Branching and Merging
  2. Advanced Merging
  3. git-mergetool Documentation

Git Conflict Glossary (Quick Reference)

  • BASE: The common ancestor of the two branches being merged.
  • LOCAL: Your current branch (the one you’re merging into).
  • REMOTE: The branch you’re merging from.
  • ours: Refers to LOCAL in conflict resolution.
  • theirs: Refers to REMOTE in conflict resolution.
  • Merge strategy -s recursive: Git’s default merge strategy that tries to automatically resolve conflicts using a three-way merge.

If you’ve got a merge war story, I’d like to hear it. Or better yet, avoid one, by rebasing early and often. Don’t let your codebase become a cautionary tale.