GitMerge Conflicts

Merge Conflicts

Merge conflicts happen when two branches change the same lines of the same file in different ways. Git cannot tell which version you want, so it pauses the merge and asks you to choose. Conflicts feel scary the first time, but the procedure is short and very reliable: open the file, pick the right content, mark the conflict resolved, and continue.

What a conflict looks like

The merge pauses

Bash
git merge feature-x
# Auto-merging src/login.js
# CONFLICT (content): Merge conflict in src/login.js
# Automatic merge failed; fix conflicts and then commit the result.

git status confirms

Bash
git status
# You have unmerged paths.
#   (fix conflicts and run "git commit")
#   (use "git merge --abort" to abort the merge)
#
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#         both modified:   src/login.js
What is inside a conflicted file

Conflict markers

Text
function greet(name) {
<<<<<<< HEAD
  return `Hello, ${name}!`;
=======
  return `Hi there, ${name}.`;
>>>>>>> feature-x
}

The markers mean:

  • <<<<<<< HEAD — start of the version from your current branch.

  • ======= — separator.

  • >>>>>>> feature-x — end of the version from the branch you are merging in.

The resolution procedure
  • Open the conflicted file in your editor.

  • Decide what the final content should be — keep one side, combine the two, or rewrite the whole thing.

  • Remove all <<<<<<<, =======, and >>>>>>> lines.

  • Save the file.

  • Run git add <file> to mark it resolved.

  • When all conflicts are resolved: git commit to finalise the merge.

Resolution in commands

Bash
# 1. Edit src/login.js to your satisfaction (no more <<< or >>>)

# 2. Mark it resolved
git add src/login.js

# 3. Continue the merge
git commit
# (Git auto-fills a merge commit message — edit if desired)
Checking what is left

Bash
git diff --name-only --diff-filter=U
# Lists every file that still has conflicts

# Or just:
git status
# Files under "both modified" still need resolving
Aborting a merge

Bail out completely

Bash
git merge --abort
# Restores the repo to exactly how it was before 'git merge'

Useful when you realise the merge is going to be too painful and you want to think about it first (maybe rebase or split into smaller changes).

Resolution helpers

Pick a whole side

Bash
# Take the version from the current branch (HEAD)
git checkout --ours  src/login.js

# Take the version from the incoming branch
git checkout --theirs src/login.js

# Then mark resolved
git add src/login.js
--ours vs --theirs during merge
During a regular merge, `--ours` is whichever branch you were on before merging; `--theirs` is the one you’re merging in. (It’s **flipped** during a rebase — see Rebase Conflicts.)
Using a merge tool

Launch the configured visual merge tool

Bash
git mergetool
# Opens a side-by-side diff for each conflicted file
# Choose hunks, save, and the tool marks them resolved
Verifying before you commit

Bash
# Make sure no markers slipped through
git diff --check
# Reports any remaining conflict markers

# Run your tests! Conflicts that compile are still wrong if behaviour broke.
Why conflicts happen — common patterns
  • Both branches edited the same function — most common, especially in long-running feature branches.

  • Lockfiles (package-lock.json, yarn.lock, Gemfile.lock) — easiest to regenerate: pick --theirs or --ours, then re-run the install command.

  • Both branches renamed the same file differently — Git flags both and asks you to pick.

  • Both branches deleted the same file differently — a “delete/modify” conflict.

Reducing conflict pain
  • Keep feature branches short-lived — fewer divergent commits, fewer conflicts.

  • Rebase or merge main into your branch frequently so conflicts surface in small batches.

  • Coordinate large refactors so two people are not editing the same files simultaneously.

  • Turn on git config --global rerere.enabled true — Git will remember your conflict resolutions and replay them automatically next time.

Tip
After resolving conflicts, run your tests before committing the merge. A conflict-free *compile* is not the same as correct *behaviour* — sometimes both sides edited the same function in ways that combine cleanly syntactically but produce wrong logic.