Git, CI/CD & GitOpsI · Version Control FoundationsFoundations
Collaboration and conflict — concurrent edits and the merge boundary
What you'll learn
- Explain how Git derives a three-way merge from two commits and their common ancestor
- Identify the conditions under which Git can resolve a merge automatically and when it must surface a conflict
- Recognise why a merge conflict is a feature, not a failure, and trace it back to a semantic ambiguity only humans can resolve
- Distinguish a textual conflict from a semantic conflict and explain why the former is what Git detects
- Apply the rule that collaboration is built on top of snapshots, not the other way around
Prerequisites
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
Git’s collaboration model is built on top of snapshots. Two engineers working from the same commit can each produce a new commit; when the repository is asked to combine them, Git looks at the two commits, walks back to the common ancestor, and performs a three-way merge. When the two sides changed different parts of the same file, the merge is mechanical. When they changed the same lines, the merge is a question only a human can answer, and Git stops and asks.
The three-way merge
When you merge two commits, Git identifies their merge base — the most
recent commit that is an ancestor of both. It then compares the state
of each side against the base, line by line, and applies the
non-conflicting changes from both sides into the result. This is the
same algorithm that git merge, git rebase, and git cherry-pick
all use, because the algorithm is just the question “what did each
side change, relative to the common ancestor?”.
flowchart TB
A["Base\ncommit B"] --> C["Branch A\ncommit A"]
A --> D["Branch B\ncommit B2"]
C --> E["Merge commit"]
D --> E
E --> F["Three-way merge\nA vs B vs base"]
The merge base is found by walking the parent pointers of both commits backwards until a common ancestor is reached. For two branches that diverged from a single commit, the base is that commit. For more complex histories (an octopus merge, a recurring branch), Git selects the “best” common ancestor heuristically.
# Find the merge base of two branches
git merge-base feature-a feature-b
# Show the merge result without committing
git merge-tree --write-tree feature-a feature-b
What Git can resolve
A merge is automatic when the two sides changed different hunks of the same file, or when only one side changed a file at all. The rule is local to each hunk: if only one side touched the lines, the change is unambiguous; if both sides touched different lines, the changes can be combined. The pattern Git is implementing is “merge if the intersection of edited line ranges is empty”.
Git can also resolve clean rename-and-modify combinations: if main
renamed policy.tf to iam.tf and feature edited policy.tf, Git
detects the rename via similarity scoring and applies the edit to the
new path. This is the same lexical analysis that powers git log --follow.
What Git cannot resolve
A merge conflict is what Git produces when the two sides edited the same lines, deleted a file that the other side modified, or one side modified a file while the other deleted it. In every case, the conflict is a marker in the working tree that says “this hunk has two competing edits and I cannot pick one for you”.
sequenceDiagram
participant M as Merging side
participant G as Git
participant H as Human
M->>G: git merge feature
G->>G: find merge base
G->>G: compare hunks
alt hunk ranges disjoint
G-->>M: merge commit
else hunk ranges overlap
G->>H: write conflict markers
H->>G: edit and git add
G-->>M: merge commit
end
The conflict markers in the file are a literal text-based reproduction
of both sides’ edits, with <<<<<<<, =======, and >>>>>>> fences.
The engineer reads the conflict, decides what the combined file should
contain, removes the markers, and stages the result. The decision is
deliberately not made by the system: a textual conflict is a signal
that the human needs to make a semantic decision.
# See the files Git marked as conflicted
git diff --name-only --diff-filter=U
# Inspect the conflict hunks for a file
git diff --check "$PATH"
Merge versus rebase
Two ways to integrate a branch into main are merge and rebase. A
merge produces a merge commit; the branch’s history is preserved. A
rebase replays the branch’s commits on top of main, producing a
linear history with no merge commit. The trade-off is not technical
correctness — both produce the same working tree — but history shape:
merges preserve the topology of “this work happened in parallel”; rebase
erases it. Infrastructure repositories usually prefer merge, because
the preservation of the parallel history is itself audit information.
Production discipline
Two rules apply universally in infrastructure repositories:
- Branch protection is non-negotiable. Every change to
mainarrives through a pull request with at least one reviewer and a green CI run. The merge boundary is the production control point; removing it is removing the gate. - Conflict resolution is a pair-review. When a merge conflict is non-trivial, the resolution should be visible to (and ideally authored by) the original author of one of the sides. The conflict encodes a semantic question about the system; the engineer who understands the system is the one who can answer it.
Cross-course references
- Linux for Production Sysadmins - Part XXVIII (ChangeMgmt) covers the dual-control principle that mirrors the pair-review of merge conflicts.
- Ansible for Production Sysadmins - Part XXXVII (RepoArch) discusses when to merge versus rebase in Ansible role repositories.
- Terraform for Production Sysadmins - Part XIX (PR) discusses Terraform plan files as a merge artefact, and how a merge conflict in a plan file is a different kind of conflict than a merge conflict in HCL.
Quiz
Knowledge check · 4 questions
Q1. Why does Git need a merge base to perform a three-way merge?
Q2. If Git applies a merge without producing a conflict, the resulting code is not guaranteed to be semantically correct.
Q3. Name one operation in Git that uses the same three-way merge algorithm as `git merge` but is not itself a merge.
Q4. Two engineers each open a pull request to add a new IAM policy statement to the same Terraform file. Both branches diverge from `main` at commit `a1b2c3`. Diagnose the merge outcome and recommend a workflow that prevents the worst-case.
Engineer A's branch adds a new `aws_iam_policy_document` data source block at the top of `policies.tf`. Engineer B's branch adds a new `aws_iam_policy_document` data source block at the bottom of the same file. Both branches rebase against `main` cleanly. The repository is configured to require one reviewer but no CI run for Terraform plans. The pull requests are opened in parallel and approved independently. When the second PR merges, the result is two policy blocks concatenated, and the plan output is not inspected before merge.
Passing score: 75%. Answers are checked in this browser.