SovranCode
HomeCourses Git & GitHub Git Merge Conflicts
This device
Course contentsGit Merge Conflicts · 41 topics

1. Git Fundamentals

Git IntroductionGit Version ControlGit vs GitHubGit InstallationGit Working TreeGit RepositoryGit CommitGit .gitignoreGit Project: Learning Journal

2. Git History

Git LogGit DiffGit ResetGit RevertGit StashGit Project: Messy Repository

3. Branches & History

Git BranchGit MergeGit Merge ConflictsGit RebaseGit Cherry-PickGit TagsGit Project: Feature Branch

4. Remotes and GitHub

Git RemoteGitHub AuthenticationGitHub RepositoryGitHub ForkGitHub IssuesGitHub PagesGit Project: Two Clone Sync

5. GitHub Collaboration

GitHub Pull RequestsGitHub Code ReviewGit Branching WorkflowGitHub Branch ProtectionGitHub CollaborationGitHub ContributingGit Project: Reviewed Pull Request

6. Automation and Professional Git

GitHub ActionsGit InternalsGit RecoveryGitHub SecurityGit Project: Team Repository CIProject
Learn Git & GitHub40 complete · 1 planned

1. Git Fundamentals

Git IntroductionGit Version ControlGit vs GitHubGit InstallationGit Working TreeGit RepositoryGit CommitGit .gitignoreGit Project: Learning Journal

2. Git History

Git LogGit DiffGit ResetGit RevertGit StashGit Project: Messy Repository

3. Branches & History

Git BranchGit MergeGit Merge ConflictsGit RebaseGit Cherry-PickGit TagsGit Project: Feature Branch

4. Remotes and GitHub

Git RemoteGitHub AuthenticationGitHub RepositoryGitHub ForkGitHub IssuesGitHub PagesGit Project: Two Clone Sync

5. GitHub Collaboration

GitHub Pull RequestsGitHub Code ReviewGit Branching WorkflowGitHub Branch ProtectionGitHub CollaborationGitHub ContributingGit Project: Reviewed Pull Request

6. Automation and Professional Git

GitHub ActionsGit InternalsGit RecoveryGitHub SecurityGit Project: Team Repository CIProject
PREVIOUS LESSONGit Merge
NEXT LESSONGit Rebase
3. Branches & History 85 min

Git Merge Conflicts

Read conflict markers, choose a correct combined result, and finish the merge with a message that records the decision.

What you will leave with

You will be able to explain why a merge stopped, identify every unmerged path, inspect base/ours/theirs index stages, resolve text and file-level conflicts, test the combined behavior, continue the merge, and abort when the intended result is not yet known.

Conflict resolution is integration design

DiagnoseRead status, unmerged entries, branch history, and the original commits.
InterpretDistinguish merge base, current side, incoming side, and working markers.
ResolveProduce one intentional final tree rather than choosing labels mechanically.
VerifyCheck the index, markers, tests, merge parents, and clean final status.

Why Git can merge some edits but not others

Git performs a three-way merge using the merge base, the current branch tip, and the other branch tip. It can combine independent changes automatically. It stops when those inputs do not determine one safe final result.

AUTO-MERGE

Independent changes

Main changes the README while feature adds a source file, or both edit clearly separate context Git can combine.

CONFLICT

Ambiguous result

Both sides change overlapping lines differently, one deletes what the other edits, or file operations disagree.

A conflict is not repository corruption

Git has preserved the competing inputs and paused before creating a misleading commit. You can inspect, resolve, or abort.

Prepare before reproducing a conflict

Use a disposable repository for this lesson. Begin with a clean state and record the graph, merge base, unique commits, and feature patch before merge.

git status
git branch --show-current
git log --oneline --decorate --graph --all
git merge-base main feature/config
git log main..feature/config --oneline
git log feature/config..main --oneline
git diff main...feature/config

Also run the repository’s tests before merging. A failing baseline makes it impossible to attribute later failures to the resolution.

Read the repository while merge is in progress

When merge stops, HEAD still points to the current branch’s pre-merge tip. Git records merge metadata, populates unmerged index stages, and writes conflict markers or file-level states into the working tree.

git status
git diff --name-only --diff-filter=U
git ls-files -u

# Inspect the three indexed stages for one conflicted path
git show :1:src/config.js   # merge base
git show :2:src/config.js   # ours: current branch
git show :3:src/config.js   # theirs: branch being merged

Stage 1 · base

The common ancestor version used to determine what each side changed.

Stage 2 · ours

The version from current HEAD—the destination branch in this normal merge.

Stage 3 · theirs

The version from the branch named in the merge command.

Working tree

Git’s attempted merge result, including markers where human intent is still required.

Ours and theirs are operation-relative

The labels above describe a normal merge. During rebase or other operations, their practical meaning can surprise you. Always identify the operation, current HEAD, and commits rather than trusting labels alone.

QUICK CHECK

Test what you learned

Type the command that prints the merge-base version of conflicted src/config.js from index stage 1.

Read conflict markers as a question

<<<<<<< HEAD
export const timeout = 3000;
export const retries = 2;
=======
export const timeout = 5000;
export const retries = 4;
>>>>>>> feature/config

<<<<<<< HEAD

Begins the current branch’s conflicting region.

=======

Separates current-side content from incoming-side content.

>>>>>>> feature/config

Ends the incoming branch’s conflicting region.

Final result

May use ours, theirs, both, or new content. Remove all markers and express intended current behavior.

The labels do not explain why each side changed. Inspect the commits and surrounding code. A timeout and retry policy may need a third result agreed by current requirements rather than either literal block.

Recover intent before editing

git log --oneline --left-right main...feature/config
git show <main-side-commit> -- src/config.js
git show <feature-side-commit> -- src/config.js
git blame <merge-base> -- src/config.js

# Compare each side with the base:
git diff <merge-base> main -- src/config.js
git diff <merge-base> feature/config -- src/config.js
  1. 01
    Name the invariant

    What behavior must the integrated program preserve?

  2. 02
    Read both reasons

    Inspect commit messages, tests, issues, and neighboring changes from each side.

  3. 03
    Design the final content

    Write the current intended behavior, even if neither side contains it verbatim.

  4. 04
    Check dependent paths

    A textual conflict may imply updates to tests, docs, schemas, generated files, or callers.

Resolve, stage, and inspect

# Edit each conflicted file to the intended final content
git diff

# Mark resolved paths by staging their final content
git add src/config.js

# Prove no unmerged entries or markers remain
git diff --name-only --diff-filter=U
git diff --cached
git diff --cached --check

# Run relevant tests, then finish
git merge --continue

Staging a path tells Git that its current working content is the proposed resolution. It does not certify correctness. The unmerged stages are replaced by an ordinary stage-0 index entry, which must be reviewed like any other commit proposal.

Continue only after every path is resolved

git diff --name-only --diff-filter=U must be empty, cached diff must contain the intended integration, and relevant tests must pass before git merge --continue.

Use whole-side selection only when it matches intent

# Replace one conflicted working file with a complete side:
git restore --ours -- src/config.js
git restore --theirs -- src/config.js

# Then inspect, edit if needed, and stage deliberately:
git diff -- src/config.js
git add src/config.js

These commands replace the whole conflicted file in the working tree. They do not merge individual hunks, explain product intent, stage automatically, or prove tests. Use them only when one complete side is the correct file-level result.

Never resolve every file by one label reflexively

“Accept all incoming” can silently discard current fixes; “accept all current” can erase the feature being integrated. Resolve path by path and requirement by requirement.

Recognize conflicts beyond markers

Both modified

Both sides changed overlapping content. Inspect all three stages and produce a combined file.

Add/add

Both sides created the same path differently. Choose or combine content and verify why one path should represent both intentions.

Modify/delete

One side removed a path while the other edited it. Decide whether the integrated product still needs the file and where its behavior belongs.

Rename-related

One or both sides renamed a path while changing related content. Confirm final names, imports, references, and duplicate leftovers.

Binary conflict

Line markers may not help. Select or regenerate the intended artifact using source, ownership, and project policy.

File/directory conflict

One side’s file collides with another side’s directory path. Design a valid final tree and update references.

Resolve file-level states explicitly

For a modify/delete conflict, status names which side deleted and which modified. Do not assume deletion wins because there are no ordinary markers.

git status
git ls-files -u -- path/to/file

# Keep an edited/restored final file:
git add path/to/file

# Or confirm deletion in the final tree:
git rm path/to/file

git diff --cached --name-status

When keeping behavior under a new path, stage the complete rename/add/delete shape and check every import, link, and test that refers to the old location.

Abort when the correct result is unknown

git status
git merge --abort
git status
git log --oneline --decorate --graph --all

Abort attempts to reconstruct the pre-merge state. It is most reliable when the merge began with a clean working tree and index. Complex pre-existing local changes can make exact reconstruction harder, which is another reason to preserve them before merging.

Aborting is progress when intent is missing

Return to a known state, clarify requirements, add tests on each branch, or reduce the integration scope. A guessed merge commit is not better than a paused merge.

Verify syntax, semantics, and history

  1. INDEX
    No unmerged entries

    Unmerged-path filters and git ls-files -u produce no entries.

  2. TEXT
    No markers or whitespace errors

    Search tracked source and run cached diff check before committing.

  3. BEHAVIOR
    Relevant tests pass

    Cover both branch intentions and the integration boundary, not only syntax.

  4. HISTORY
    Merge parents are correct

    The final merge commit names the expected destination and feature tips as parents.

  5. STATE
    Working tree is clean

    No accidental files, unresolved paths, or unrelated edits remain.

git diff --name-only --diff-filter=U
git ls-files -u
git diff --cached --check
git grep -n -e '^<<<<<<< ' -e '^=======$' -e '^>>>>>>> '

# After tests and merge completion:
git show --no-patch --pretty=raw HEAD
git show --stat --first-parent HEAD
git status

The marker search is a useful guard, not universal proof: legitimate documentation can contain marker-like examples. Review every match in context.

Write a merge message that records the decision

A useful merge message names the integrated branch or capability and explains non-obvious resolution choices. Do not claim “resolved conflicts” without stating which behavior was preserved and why.

WEAK

fix merge

Does not identify the feature, conflict, invariant, or verification.

USEFUL BODY

Record the integration choice

“Merge checkout timeout policy. Keep main’s cancellation signal and feature’s bounded retry count; integration tests cover both paths.”

Optional: reuse recorded resolutions carefully

git rerere can record how a conflict shape was resolved and reuse that resolution when the same conflict reappears. Enable it only when you understand that reused content still requires review and tests.

git config rerere.enabled true
git rerere status
git rerere diff

Rerere reduces repeated typing; it does not know whether product requirements changed since the earlier resolution.

Guided practice: resolve a real conflict

Use a disposable repository. Create a base configuration and test, then branch. On main, change timeout handling and its test. On feature, change retry behavior in the same function and its test.

  1. 01
    Record both intentions

    Inspect each branch’s unique commit and prove its tests pass independently.

  2. 02
    Predict the conflict

    Compare each side with the merge base and identify overlapping lines before merging.

  3. 03
    Trigger and inspect

    Merge into main, read status, list unmerged stages, and print base/ours/theirs.

  4. 04
    Design combined behavior

    Preserve cancellation and bounded retries, update tests, remove markers, and stage exact paths.

  5. 05
    Verify and continue

    Run marker checks, cached review, all tests, continue, inspect both parents, and prove clean status.

Independent lab: resolve four conflict classes

Create a disposable repository with separate scenarios for:

  1. A same-line content conflict requiring a combined result.
  2. An add/add conflict where both files contain wanted sections.
  3. A modify/delete conflict where behavior moves to a replacement path.
  4. A rename-related conflict requiring updated imports and no duplicate old path.
  5. For every scenario, capture the pre-merge graph, merge base, unique commits, three index stages where available, final cached diff, tests, and merge parents.
  6. Abort one first attempt deliberately, prove restoration, then rerun and resolve from clarified requirements.
  7. Finish with clean status and a short resolution log stating the invariant preserved in each merge.
Definition of done

No marker or unmerged entry remains, each final tree preserves documented intent from both histories, tests exercise the integration boundary, and every merge commit has the expected parent pair.

Common conflict-resolution mistakes

Deleting markers and stopping

Marker removal is not semantic resolution. Inspect, stage, test, and verify history.

Choosing a side by label

Ours/theirs describe operation roles, not quality or ownership.

Ignoring the merge base

The base reveals what each side changed and prevents mistaking old shared content for one side’s intent.

Checking only conflicted files

Auto-merged dependent files can still produce invalid combined behavior.

Committing unrelated edits

Keep the merge resolution focused. Review the complete index before continuing.

Forcing through uncertainty

Abort, clarify requirements, and add tests rather than inventing an unsupported result.

Lesson review

You can now treat merge conflicts as three-way integration problems. The base, current side, and incoming side provide evidence; the final file expresses present intent; the staged tree, tests, parent graph, and clean status prove the resolution.

  • I can inspect stage 1, 2, and 3 versions of an unmerged path.
  • I read commit intent and dependent behavior before editing markers.
  • I resolve file-level conflicts as deliberate final-tree decisions.
  • I know when to continue and when to abort the merge.
  • I verify unmerged entries, markers, cached content, tests, parents, and final status.

Related lessons

  • Git Merge — Conflicts appear only when Git cannot combine both sides.
  • Git Rebase — Rebase can also stop with conflicts, one commit at a time.
KNOWLEDGE CHECK

Check your conflict-resolution model

Use three-way evidence to choose, combine, verify, continue, or abort.

01Why does Git stop with a content conflict?
02During a normal merge on main, what does 'ours' mean?
03What does index stage 1 contain for an unmerged path?
04What marks a conflicted file as resolved?
05Why is choosing all 'ours' or all 'theirs' risky?
06What does git merge --abort attempt to do?
07When is a merge conflict truly resolved?
PREVIOUS LESSONGit Merge
NEXT LESSONGit Rebase
ON THIS PAGEGit Merge ConflictsConflict resolution is integration designWhy Git can merge some edits but not othersPrepare before reproducing a conflictRead the repository while merge is in progressRead conflict markers as a questionRecover intent before editingResolve, stage, and inspectUse whole-side selection only when it matches intentRecognize conflicts beyond markersResolve file-level states explicitlyAbort when the correct result is unknownVerify syntax, semantics, and historyWrite a merge message that records the decisionOptional: reuse recorded resolutions carefullyGuided practice: resolve a real conflictIndependent lab: resolve four conflict classesCommon conflict-resolution mistakesLesson reviewKnowledge checkRelated lessons
Course contents