|$ curl https://forge-ai.dev/api/markdown?path=docs/git/conflicts
$cat docs/git-—-conflict-resolution.md
updated Recently·12 min read·published

Git — Conflict Resolution

GitBeginner
Introduction

Merge conflicts are an inevitable part of collaborative development. They occur when Git cannot automatically reconcile changes from two branches that modified the same part of a file in different ways.

While conflicts can seem intimidating at first, understanding how they work and having a systematic resolution strategy makes them straightforward to handle. This guide covers everything from conflict markers to advanced tools like git rerere.

What Causes Conflicts?

Git is remarkably good at merging. It can automatically merge changes to different parts of a file, renames, and even formatting changes. But three scenarios always cause conflicts:

Same Line, Different Changes

Two branches modify the exact same line of a file in different ways. Git does not know which version to keep.

One Deletes, One Modifies

One branch deletes a section that another branch modifies. Git cannot decide whether to keep the deletion or the modification.

Adjacent Lines Changed

Changes on adjacent lines in different branches can sometimes cause ambiguity, especially with function boundaries or import statements.

Conflict Markers Explained

When a conflict occurs, Git inserts conflict markers into the affected file. Understanding these markers is the first step to resolving any conflict.

conflict-example.js
TEXT
1# Conflict marker structure in a file:
2<<<<<<< HEAD
3const MAX_RETRIES = 3;
4const TIMEOUT = 5000;
5=======
6const MAX_RETRIES = 5;
7const TIMEOUT = 10000;
8const BACKOFF = 1.5;
9>>>>>>> feature/config
10
11# Marker breakdown:
12# <<<<<<< HEAD -- Start of "our" changes (current branch)
13# ======= -- Separator between "ours" and "theirs"
14# >>>>>>> feature/config -- End of "their" changes (incoming branch)
15
16# For rebase conflicts (opposite semantics):
17# <<<<<<< HEAD -- The changes being replayed
18# ======= -- Separator
19# >>>>>>> main -- The base branch changes

info

During a merge, HEAD is your current branch and the other branch is marked with its name. During a rebase, the semantics are reversed: HEAD is the commit being replayed, and the other side is the branch you are rebasing onto.

When Git detects a conflict during a merge or rebase, it reports which files have conflicts and pauses the operation:

terminal
Bash
1# Merge conflict output
2git merge feature/config
3# Auto-merging src/config.js
4# CONFLICT (content): Merge conflict in src/config.js
5# Automatic merge failed; fix conflicts and then commit.
6
7# Check which files have conflicts
8git status
9# On branch main
10# You have unmerged paths.
11# (fix conflicts and run "git commit")
12# (use "git merge --abort" to abort the merge)
13#
14# Unmerged paths:
15# both modified: src/config.js
16
17# View the conflict around problematic regions
18git diff
19# Shows the conflict markers and surrounding context
Manual Conflict Resolution

The most fundamental way to resolve conflicts is to edit the file directly. Here is a systematic approach:

terminal
Bash
1# Step 1: Open the conflicting file in your editor
2# Look for <<<<<<<, =======, >>>>>>> markers
3
4# Step 2: Edit the file to resolve each conflict
5# Before:
6<<<<<<< HEAD
7const MAX_RETRIES = 3;
8=======
9const MAX_RETRIES = 5;
10const BACKOFF = 1.5;
11>>>>>>> feature/config
12
13# After resolving (keep both, with our retry count but add backoff):
14const MAX_RETRIES = 3;
15const BACKOFF = 1.5;
16
17# Step 3: Remove all conflict markers, including <<<<<<<, =======, >>>>>>>
18
19# Step 4: Stage the resolved file
20git add src/config.js
21
22# Step 5: Continue the operation
23git commit # for merge
24# OR
25git rebase --continue # for rebase
26
27# Check if all conflicts are resolved
28git diff --check
🔥

pro tip

Use git diff --check to find leftover conflict markers before committing. It highlights lines that still contain conflict markers and whitespace errors, preventing accidental commit of unresolved conflicts.
Using git mergetool

git mergetool launches a visual merge tool that displays the three-way diff: the common ancestor, the current branch (local), the incoming branch (remote), and the merged result.

terminal
Bash
1# Configure your preferred merge tool
2git config --global merge.tool vscode
3git config --global mergetool.vscode.cmd "code --wait $MERGED"
4
5# Common merge tools:
6# - vscode : Visual Studio Code
7# - meld : Visual diff/merge tool
8# - kdiff3 : KDiff3 (cross-platform)
9# - p4merge : Perforce Merge
10# - beyondcompare: Beyond Compare
11
12# Launch the merge tool for conflicting files
13git mergetool
14
15# Launch for a specific file
16git mergetool src/config.js
17
18# Don't prompt before launching
19git mergetool --no-prompt
20
21# Keep backup .orig files (or disable them)
22git config --global mergetool.keepBackup false
23
24# After resolving in the tool, save and close
25# Git automatically stages the resolved file

When git mergetool launches, it typically shows three panes:

PaneContentLabel
LeftYour current branch's versionLOCAL
CenterThe merged result (editable)MERGED
RightThe incoming branch's versionREMOTE
Bottom (sometimes)The common ancestorBASE
git rerere -- Reuse Recorded Resolution

git rerere ("reuse recorded resolution") remembers how you resolved a conflict and automatically re-applies that resolution the next time the same conflict occurs. This is invaluable for long-lived branches that are rebased frequently.

terminal
Bash
1# Enable rerere globally
2git config --global rerere.enabled true
3
4# Or enable per-repository
5git config rerere.enabled true
6
7# With rerere enabled, when you resolve a conflict:
8# 1. Git records the pre-image (conflicting state)
9# 2. You resolve the conflict
10# 3. Git records the post-image (resolution)
11# 4. Next time the same conflict occurs -> auto-resolved
12
13# Check what rerere has recorded
14git rerere status
15
16# Show the recorded resolutions
17git rerere diff
18
19# Clear rerere cache for current conflict
20git rerere clear
21
22# Forget a specific resolution
23git rerere forget src/config.js
24
25# List all recorded resolutions
26ls -la .git/rr-cache/
27
28# Rerere is especially useful with:
29# - Long-lived feature branches
30# - Rebasing feature branches onto main
31# - Cherry-picking between branches

best practice

Enable rerere.enabled globally -- it has no downside and can save enormous time. When rebasing a feature branch with dozens of commits, rerere automatically handles conflicts you already resolved in previous rebase attempts.
Aborting & Restarting Merges

Sometimes a merge or rebase goes wrong and you need to start over. Git provides clean abort mechanisms for both operations.

terminal
Bash
1# Abort a merge (returns to pre-merge state)
2git merge --abort
3
4# Abort a rebase (returns to pre-rebase state)
5git rebase --abort
6
7# Abort a cherry-pick
8git cherry-pick --abort
9
10# Start over with a clean merge strategy:
11# If you know a merge will be complex, try:
12git merge --no-commit feature
13# This stages the merge but doesn't commit it,
14# allowing you to inspect and fix issues before committing.
15
16# Use --strategy-option to prefer one side:
17# Keep our version on conflicting files
18git merge -X ours feature
19
20# Keep their version on conflicting files
21git merge -X theirs feature
22# WARNING: These discard the other side's changes silently!
23
24# For rebase, use --strategy-option:
25git rebase -X theirs main # prefer rebased branch on conflict
26git rebase -X ours main # prefer main on conflict

warning

git merge -X theirs and git rebase -X theirsautomatically resolve conflicts by taking one side. Use these only when you are certain one side's changes are always correct -- for example, when merging a hotfix branch that should override configuration.
Strategies to Minimize Conflicts

Small, Frequent Merges

Merge or rebase with main daily. The smaller the divergence, the fewer conflicts. A feature branch that diverges for weeks is guaranteed to have painful conflicts.

Communicate About Shared Code

When working on files likely to conflict (shared configs, core modules, interfaces), coordinate with teammates. Clear communication prevents two people from making conflicting changes simultaneously.

Refactor Before Not During

If a major refactor is needed, do it in a dedicated branch, merge it to main first, then rebase feature branches. This avoids the nightmare of merging a refactor with ongoing feature work.

Use File Ownership Conventions

In larger teams, establish clear ownership of specific files or modules. When people know who owns what, accidental concurrent edits to the same file are reduced.

Automated Formatting

Use Prettier, ESLint, or Black to enforce consistent formatting. Formatting conflicts (tabs vs spaces, trailing commas) generate spurious conflicts that automated tools eliminate.

Conflict Resolution Checklist

Follow this checklist every time you encounter a merge conflict:

  • Understand the conflict: identify which lines conflict and why
  • Communicate: if the conflict involves a teammate's work, discuss the resolution
  • Edit the file: remove markers and keep the desired code
  • Test: ensure the resolved code compiles and passes tests
  • Stage: git add <file>
  • Continue: git commit (merge) or git rebase --continue
  • Don't force: if stuck, use git merge --abort or git rebase --abort
Best Practices

Enable rerere Globally

git config --global rerere.enabled true. It has no performance cost and can save hours when rebasing long-lived branches.

Commit Often, Merge Often

Small commits and frequent merges mean fewer conflicts per merge and simpler resolution when conflicts do occur.

Use Visual Merge Tools

Configure a merge tool like VS Code, Meld, or KDiff3. The three-pane view makes it much easier to understand and resolve complex conflicts.

Don't Force Push After Merge

If you resolved conflicts during a merge, never force push the result. Use a normal push and let your team pull the merge commit normally.

$Blueprint — Engineering Documentation·Section ID: GIT-CONF·Revision: 1.0