Skip to main content
RunBook Academy

Git, CI/CD & GitOpsX · Merge ConflictsConflicts

When merges conflict — the three cases the algorithm cannot resolve

Intermediate⏱ ~20 mingit

What you'll learn

  • Identify the three cases that produce a merge conflict and the algorithm response to each
  • Explain why a "same line modified by both sides" conflict is the most common case
  • Distinguish a delete/modify conflict from a modify/delete conflict and explain the resolution asymmetry
  • Recognise why binary file conflicts cannot be merged textually and require an all-or-nothing choice
  • Predict from `git status` output which case applies to each conflicted path

Prerequisites

Practice

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

A merge conflict is the answer the three-way merge produces when none of its automatic rules apply. The recursive strategy can combine two trees if and only if every conflicted path falls into one of the clean cases (unchanged-in-both, changed-in-one, changed-identically-in-both). When a path falls into the “changed-differently-in-both” category — by edit, by deletion, or by binary content — the algorithm stops and asks a human. There are exactly three cases that produce this stop. Knowing which case applies from a single git status is what turns a conflict from a stuck merge into a 30-second decision.

Case 1 — both sides modify the same lines

This is the case the previous lessons anticipated. Two branches both edited the same lines of the same file to different new values. The algorithm knows the base, the ours version, and the theirs version, and it can see that the lines differ from base in two different directions, so it cannot pick. It writes conflict markers into the working tree, stages all three blobs at the conflicted path, and prints a per-path report:

git status
# On branch feature/iam-rotation
# 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:   terraform/iam/main.tf

git diff --name-only --diff-filter=U
# terraform/iam/main.tf

The phrase “both modified” in git status is the signature of case 1. The fix is to open the file, read the conflict markers, choose the combined content, remove the markers, and stage the result with git add. Every conflict-resolver lesson in this part assumes this case unless the status output says otherwise.

flowchart TB
    A["Base file\nline: retention = 30"] --> B["Ours\nretention = 90"]
    A --> C["Theirs\nretention = 7"]
    B --> D["Conflict\nboth modified"]
    C --> D
    D --> E["Human chooses\nretention = 30 or 90 or 7\nor some new value"]

The lines outside the overlap merge automatically. Only the lines both sides touched are written into the conflict hunk. Most “both modified” conflicts in an infrastructure repository touch one or two hunks in a file with hundreds of lines; the rest of the file is already merged cleanly.

Case 2 — delete/modify and modify/delete

When one branch deletes a file and the other modifies it, the algorithm reports the conflict in a deliberately asymmetric way:

git status
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#         deleted by them:   ansible/inventory/staging.ini
#         deleted by us:     ansible/inventory/production.ini

git diff --name-only --diff-filter=U
# ansible/inventory/staging.ini
# ansible/inventory/production.ini

The two git status lines tell you which side deleted the file and which side kept it. “Deleted by them” means the branch being merged in no longer has the file, while the current branch still has it. “Deleted by us” means the current branch removed it, while the merged-in branch still has it. They are different decisions: in one case, the file’s survival is a question; in the other, the file’s contents are a question.

The repository treats this case as a per-path conflict: the file exists in the working tree, the index has entries for all three slots at that path, and the resolution is to either keep the file (git add the working tree version) or complete the deletion (git rm). The two operations are not equivalent, and the engineer must reason about which side is correct.

Case 3 — both sides modify a binary file

Git does not attempt a textual merge of files it cannot diff as text. PNG diagrams, compiled .tfstate files, vendored Go binaries, and release tarballs are all binary in Git’s eyes; when both branches change the same path, the algorithm cannot produce a unified diff, much less a merged file:

git status
# Unmerged paths:
#         both modified:   diagrams/network-topology.png
#                         architecture/state/terraform.tfstate

git diff --name-only --diff-filter=U
# diagrams/network-topology.png
# architecture/state/terraform.tfstate

The status output is identical to case 1 (“both modified”), but the resolution is fundamentally different. There are no hunks, no conflict markers, no textual history to consult. The engineer must pick one side wholesale — git checkout --ours &lt;file&gt; or git checkout --theirs &lt;file&gt; — and accept that any work on the other side is discarded. A binary conflict on a Terraform state file (covered in X-05 in depth) is the worst case because the change is not just a file edit; it is a record of cloud resources that diverged in two directions.

What git status is telling you

The per-path line in git status is a complete case identifier once you know what each phrase means:

  • both modified — case 1 (textual conflict in the file) or case 3 (binary conflict in the file). The ambiguity is resolved by opening the file; binary files contain no conflict markers, textual ones do.
  • both added — both sides added the same path. Identical content merges automatically; divergent content is a textual conflict.
  • deleted by them — case 2 (deletion on theirs, modification on ours).
  • deleted by us — case 2 (deletion on ours, modification on theirs).
  • added by them / added by us — one side added the path the other never touched. Take the added version, or reject it deliberately.

These five phrases are the entire vocabulary of a merge conflict’s per-path report. Recognising them in git status output is what makes a large conflict list navigable.

Production discipline

Three rules for recognising conflict cases in a production merge:

  1. Read git status first, not the files. The status output classifies every conflicted path by case before you open a single file. Resolving in case order (textual, then delete/modify, then binary) ensures the slower binary decisions get the engineer’s attention rather than being glossed over after a long textual resolution.
  2. Treat delete/modify as a semantic decision, not a syntactic one. A deletion in an infrastructure repository is often a production change. Confirm with the deleting branch’s author that the deletion is intended before accepting it.
  3. Run git merge-tree in CI before opening a PR. A pipeline that pre-checks the merge outcome with git merge-tree can warn the PR author that conflicts are coming before they sit down to resolve them. The earlier the warning, the cheaper the resolution.

Cross-course references

  • Linux for Production Sysadmins — Part XXVIII (ChangeMgmt) covers file deletion under change control; the delete/ modify conflict in a Git repository maps to the same dual- control principle.
  • Ansible for Production Sysadmins — Part XXXVII (RepoArch) discusses inventory file merges; the most common Ansible conflict is a delete/modify on a host group file.
  • Terraform for Production Sysadmins — Part XIX (PR) and Part XX (State) cover Terraform state file conflicts, which are case 3 binary conflicts with the highest possible blast radius in this course.

Quiz

Knowledge check · 4 questions

  1. Q1. After a merge, `git status` prints `both modified: architecture/state/terraform.tfstate`. Which conflict case is this, and what is the resolution shape?

  2. Q2. The `git status` phrase `deleted by them` means the branch that was just merged in no longer has the file, while the current branch still has it.

  3. Q3. Name the three cases that produce a merge conflict, and the `git status` phrase that distinguishes a case-2 conflict from a case-1 conflict.

  4. Q4. Diagnose the conflict cases in a multi-file IaC merge and recommend the order of resolution.

    An engineer merges `feature/iam-rotation` into `main`. `git status` lists five conflicted paths: `terraform/iam/main.tf` shows `both modified`, `ansible/inventory/staging.ini` shows `deleted by them`, `ansible/inventory/production.ini` shows `deleted by us`, `diagrams/network-topology.png` shows `both modified`, and `architecture/state/terraform.tfstate` shows `both modified`. The merged-in branch is two weeks old; the current branch has been edited daily.

Passing score: 75%. Answers are checked in this browser.