Practice: Merge conflicts
Self-check questions
Section titled “Self-check questions”Answer each in your own words first, then open the answer to check.
Q1. A teammate says “I got a merge conflict; I’m worried I broke something irrecoverable.” Without writing code, explain why their worry is unfounded and what’s actually happening.
Show answer
The worry is unfounded because nothing has been destroyed. Both branches’ commits still exist; both are in git’s history. The conflict is just git stopping mid-merge to ask “which version of these lines do you want?”; the question doesn’t damage anything. git merge --abort undoes the merge entirely if they panic. The mechanics of resolution are six rote steps (recognize, find, decide, edit, stage, commit). The fear comes from not having a roadmap; once they read the markers and understand the structure, it’s mechanical.
Q2. Explain the diff3 conflict style. What does it show that the default style doesn’t, and why is that strictly better?
Show answer
Diff3 conflict style adds a third section to the conflict marker showing the common ancestor (the original version of the lines before either branch touched them). The default style shows only “your version” and “their version”; you have to remember or guess what the original looked like. With diff3, the original is right there in the file. The strictly-better claim: in nearly every conflict, knowing the ancestor makes the right resolution obvious. The ancestor tells you “what was here,” which often clarifies “what was each branch trying to change.” Enable once with git config --global merge.conflictstyle diff3; never look back.
Q3. Walk through the six-step resolution process for a textual conflict, in order, in your own words.
Show answer
(1) Recognize: run git status after the failed auto-merge; observe the “Unmerged paths” section listing files with conflicts. (2) Find: open each conflicting file in your editor; search for <<<<<<< to navigate to each conflict region. A file may have multiple regions; each one independently needs resolving. (3) Decide: for each region, ask “what should the final code look like?”; your version, their version, both combined, or something new. (4) Edit: write the resolved content where the conflict was; delete ALL marker lines (<<<<<<<, |||||||, =======, >>>>>>>). The file should look like normal code with no markers. (5) Stage: git add <file> tells git you’ve resolved that file. Repeat for every conflicting file. (6) Commit: git commit opens an editor with a pre-filled merge commit message; accept or edit; save; close. The merge is complete.
Q4. A semantic conflict shipped to production and broke a feature. Identify (a) why git didn’t catch it, (b) what disciplines would have caught it pre-merge, and (c) why semantic conflicts are the more dangerous category.
Show answer
(a) Why git didn’t catch it: the two branches touched DIFFERENT files. Git’s auto-merge only checks for textual overlap; semantic dependencies (one change assumes the other behaves a certain way) are invisible. Both branches merged clean. (b) What would have caught it pre-merge: (i) integration tests that exercise both files together; a test of “refund flow with validation” would have caught the example in the lesson. (ii) Code review that thinks about the merged result, not just each PR in isolation. (iii) CI that runs the full test suite, not per-file tests. (iv) Short-lived branches that merge often, reducing the surface for semantic divergence. (c) Why semantic conflicts are more dangerous: they ship to production silently. Textual conflicts force a human to look at the merge; semantic conflicts don’t. They cause the “two PRs that both passed review broke production” failure mode. Mature teams invest in catching semantic conflicts because the cost of missing them is high.
Q5. You’re 30 minutes into resolving a complex merge. You realize the right answer is “rebase first, then merge.” Should you git merge --abort and start over, or push through? Walk through the decision.
Show answer
Push through OR abort; it depends on the resolution work invested. If you’ve invested significant time and the resolutions are mostly done, finish: aborting throws away the work. If you’ve barely started and the conflict surface is overwhelming, abort: rebase onto main, then re-attempt the merge. The middle case (30 min in, still complex): often easier to abort than to finish, because the resolution decisions you’ve made may be wrong if a rebase-first approach would have produced different conflicts entirely. The judgment call: how much of your resolution work would carry over to the rebased version? If most carries (your decisions don’t depend on the merge approach), finish. If little carries (your decisions are merge-specific), abort. When in doubt, abort and rebase; the do-over is cheap relative to the cost of a wrong resolution.
Conflict creation drill
Section titled “Conflict creation drill”This drill teaches conflict resolution by manufacturing conflicts intentionally. Use a sandbox repository.
Drill 1: Create a textual conflict:
- Initialize a sandbox:
git init conflict-sandbox && cd conflict-sandbox - Create
hello.txtwith one line:Hello, world. Stage + commit. - Create branch A:
git switch -c branch-a. Edithello.txttoHello, branch A. Stage + commit. - Switch back to main:
git switch main. Create branch B:git switch -c branch-b. Edithello.txttoHello, branch B. Stage + commit. - Switch back to main:
git switch main. Merge branch-a:git merge branch-a. Should succeed. - Now merge branch-b:
git merge branch-b. Conflict! git status: observe the “Unmerged paths” section.- Open
hello.txt. Observe the conflict markers. - Resolve to
Hello, world from both branches. Save. git add hello.txt.git commit. Done.
Drill 2: Turn on diff3 and observe:
git config --global merge.conflictstyle diff3- Repeat the drill above with a fresh sandbox.
- Observe the third section (
||||||| ancestor) showing the originalHello, world. - Notice how seeing the ancestor makes the resolution obvious.
Drill 3: Abort and retry:
- Repeat the textual conflict drill up through step 6 (where the conflict happens).
- Instead of resolving:
git merge --abort. git status: observe you’re back to clean state.- Now make a small change in main and commit (something unrelated, like a new file).
- Try the merge again:
git merge branch-b. Notice the same conflict reappears (the underlying issue didn’t go away just because you aborted). - Resolve it this time. Commit.
Drill 4: Delete-modify conflict:
- Fresh sandbox. Create
feature.txtwith content. Commit. - Create
branch-delete: deletefeature.txt.git rm feature.txt. Commit. - Switch back to main. Create
branch-modify: editfeature.txt. Commit. - Switch back to main. Merge branch-delete (succeeds, file is gone).
- Merge branch-modify: conflict.
git status: observe the “deleted by us / modified by them” indicators.- Choose:
git rm feature.txt(delete wins) ORgit add feature.txt(modify wins). git commit.
Drill 5: Practice the worked examples:
For at least three of the six worked examples in the lesson body, recreate the scenario in a sandbox and walk through the resolution. The most valuable is Example 2 (logical combine, both changes needed). The discipline of writing a test that covers the combined behavior is the load-bearing habit.
Semantic-conflict observation drill
Section titled “Semantic-conflict observation drill”This isn’t a hands-on coding drill; it’s a thought experiment. Pick a real codebase you know.
Drill 6: Identify a potential semantic conflict in a real codebase:
- Find two functions or modules that depend on each other (e.g., a validator and the code that calls it).
- Imagine two separate PRs: PR A modifies the validator to add a new constraint. PR B modifies the caller to pass a value that fails the new constraint.
- Walk through: how would these PRs merge? (Textually clean if they touch different files.) How would CI behave? (Per-file tests would pass; integration tests touching both might catch it.)
- Identify the disciplines that would have caught this: integration tests, thoughtful code review, short-lived branches.
- Write 2-3 sentences on what your real-world team does (or doesn’t do) to catch semantic conflicts.
Scenario reflections
Section titled “Scenario reflections”Scenario A. A junior teammate has gotten into the habit of running git merge --abort whenever they see a conflict, then asking a senior to “fix it.” Without confrontation, describe a path to changing this habit while still supporting them.
Show answer
Path to changing the abort-and-ask habit:
- Show them the mechanics once. Sit with them through one resolution end-to-end, slow. The fear comes from not having a roadmap; one full walkthrough provides the roadmap.
- Build their conflict reps. Suggest they deliberately create a few conflicts in a sandbox (the practice section drills are exactly this). Five sandbox conflicts later, they have the muscle memory.
- Stop fixing it for them. When they ask, offer to pair, not fix. “Let’s look at it together; what do the markers say?” This is supportive without enabling the habit.
- Praise the next solo resolution. When they resolve a conflict on their own, recognize it. Pattern reinforcement. The discipline: support without enabling. Senior engineers fixing every conflict for a junior is well-intentioned but blocks their growth.
Scenario B. Your team’s average PR has 3+ merge conflicts per merge attempt. Without naming individuals, identify the most likely upstream causes and the three highest-leverage process changes.
Show answer
Three highest-leverage process changes:
- Reduce branch lifetime. If PRs are landing 1-2 weeks after opening, the conflict rate is structural; branches diverge from main during the wait. Aim for 1-3 day PR lifetimes. May require splitting larger features into multiple PRs.
- Adopt rebase-before-merge as policy. When a PR is ready to merge, rebase onto latest main first. Each PR comes to main on top of latest main, so conflicts are surfaced at PR-author time (not at merge time). Distributed cost; smaller individual conflicts.
- Identify hotspot files. Some files (config, schema, shared utilities) generate disproportionate conflict. Restructure where possible (e.g., split a giant config file into per-module configs), or assign clear ownership so only one person touches it at a time. Underlying cause if all three don’t help: lack of module boundaries or unclear team ownership. That’s a structural problem, not a process problem; raise it as architecture.
Scenario C. An AI agent on your team produced a PR that merged cleanly into main. Two days later, a different AI agent’s PR (also merging cleanly into main) broke production because both PRs implicitly assumed the same shared utility behaved differently. Walk through what kind of conflict this was and what discipline would have caught it.
Show answer
This was a semantic conflict. Both AI agents’ PRs were textually compatible (touched different files) so git’s auto-merge succeeded; but they made incompatible assumptions about how the shared utility behaved. The discipline that would have caught it:
- Integration tests at the orchestrator level. Tests that exercise both code paths together, not per-PR unit tests. The integration test of “use the utility from both code paths simultaneously” would have surfaced the conflict.
- The orchestrator reviewing the merged result, not just each PR. A human or orchestrating-agent reading both PRs together would have noticed the assumption mismatch. This is the multi-agent equivalent of code review.
- Smaller, more frequent merges. If both agents had merged within hours of each other, the surface for divergence would have been smaller. Daily integration beats weekly. The Phase 4 design principle: at multi-agent scale, semantic conflicts are inevitable. Build integration tests at the orchestrator level; review at the orchestrator level; merge often.
Flashcards
Section titled “Flashcards”Q. Why do merge conflicts happen?
Two branches changed the same lines in different ways. Git cannot mechanically decide which version you want; it stops and asks you.
Q. What are the three conflict marker lines in the default format?
<<<<<<< HEAD (start of your version), ======= (separator), >>>>>>> <other-branch> (end of their version, with the branch name).
Q. What does diff3 conflict style add?
A fourth marker ||||||| ancestor showing what the lines looked like in the common ancestor (before either branch changed them). Enable with git config --global merge.conflictstyle diff3 or the modern zdiff3.
Q. What are the six steps to resolve a textual conflict?
(1) Recognize merge state with git status. (2) Find markers in each file. (3) Decide on the resolution. (4) Edit to the resolution + delete markers. (5) Stage with git add. (6) Commit with git commit.
Q. What are the five conflict types?
(1) Textual (pick one or combine). (2) Logical-combine (both changes needed). (3) Semantic (textually clean but logically incompatible; git can’t detect). (4) Delete-modify (file existence dispute). (5) Rename-edit (structural change collides with content change).
Q. Which conflict type is git unable to detect?
Semantic conflicts. Both branches change different files, so git’s auto-merge succeeds, but the changes depend on each other in ways that break at runtime. Integration tests + thoughtful code review catch these.
Q. When should you use git merge --abort?
When the conflict surface is so large it would take hours to resolve, when you realize you should merge the other direction, when the wrong branch is checked out, when your teammate’s branch contains unexpected commits, or when you’re tired/distracted.
Q. Name three conflict prevention disciplines.
(1) Rebase your branch onto main often (small daily conflicts vs one massive one later). (2) Keep PRs small + short-lived. (3) Communicate big refactors before starting them.
Q. What is the resolution mindset?
A conflict is a question, not a disaster. Nothing is lost; both branches’ work is safe. git merge --abort undoes the merge entirely if needed. The mechanics are six rote steps.
Q. How does conflict resolution scale to multi-agent workflows?
The mechanics are the same; the frequency goes up. Each AI agent works in its own worktree (L15) and produces branches. The orchestrator (human or another agent) resolves conflicts at merge time. Semantic conflicts become the dominant failure mode, caught by integration tests at the orchestrator level.