Chapter 20Lesson 02~185 minutes

Merge Algorithms, Conflict Engineering, rerere, and Custom Merge Drivers: Guided Hands-On Workflow and Core Operations

Create disposable text, rename/delete, file/directory, semantic, and repeated-conflict scenarios; inspect merge bases/index stages; abort/retry safely; and prove rerere reuse.

Hands-on mergeIndex stagesmerge-treererere reuse

Learning objectives

  • Preview merge outcomes with merge-tree without changing the live index/worktree.
  • Inspect stage 1/2/3 entries and exact base/ours/theirs contents.
  • Abort and retry a clean lab merge, then validate the chosen resolution.
  • Diagnose structural conflicts from Git state rather than marker scanning.
  • Record and reuse a rerere resolution while automatic staging remains disabled.

1. Create a disposable multi-repository merge laboratory

mkdir git-merge-engineering-lab
cd git-merge-engineering-lab
git --version

Each scenario below uses its own repository. This prevents one conflict, rerere cache, or merge-in-progress state from contaminating another.

2. Build a deterministic text conflict

git init -b trunk text-conflict
cd text-conflict
git config user.name "Merge Lab"
git config user.email "merge-lab@example.invalid"

printf "name=api\ntimeout_ms=100\nretries=2\n" > service.conf
git add service.conf
git commit -m "base"
BASE=$(git rev-parse HEAD)

git switch -c topic
printf "name=api\ntimeout_ms=900\nretries=2\n" > service.conf
git commit -am "topic: raise timeout"
TOPIC=$(git rev-parse HEAD)

git switch trunk
printf "name=api\ntimeout_ms=250\nretries=2\n" > service.conf
git commit -am "trunk: tune timeout"
TRUNK=$(git rev-parse HEAD)

3. Inspect base and preview the merge without live-state mutation

git status --short --branch
git log --graph --decorate --oneline --all
git merge-base trunk topic
git merge-tree --write-tree trunk topic
echo "preview exit=$?"
git status --short --branch

The preview should return conflict exit status 1 while leaving branch, index, and working tree unchanged.

4. Start the real merge and inspect stage 1/2/3

git merge --no-ff topic
echo "merge exit=$?"

git status --short
git ls-files -u -- service.conf
git rev-parse HEAD
git rev-parse MERGE_HEAD
git show :1:service.conf
git show :2:service.conf
git show :3:service.conf

Expected values are 100 in stage 1, 250 in stage 2, and 900 in stage 3. HEAD remains the trunk tip while the merge is unresolved.

5. Inspect ours/theirs in the working tree, then recreate diff3 markers

git restore --ours -- service.conf
cat service.conf

git restore --theirs -- service.conf
cat service.conf

git restore --merge --conflict=diff3 -- service.conf
cat service.conf

restore --ours uses stage 2 and --theirs uses stage 3 in this normal merge. Rebase uses different role semantics, so do not generalize the labels blindly across operations.

6. Abort once on purpose

git merge --abort
git status --short --branch
git rev-parse HEAD
git rev-parse --verify MERGE_HEAD 2>/dev/null || echo "no merge in progress"

The lab started clean, so abort restores the pre-merge state. Production merges should likewise start from controlled state because abort cannot always reconstruct unrelated uncommitted work.

7. Retry, resolve, stage, test, and commit

git merge --no-ff topic || true

printf "name=api\ntimeout_ms=300\nretries=2\n" > service.conf
git add service.conf

git ls-files -u -- service.conf
test "$(sed -n 's/^timeout_ms=//p' service.conf)" -le 500

git diff --cached -- service.conf
git commit -m "merge topic: reconcile timeout"
git show -s --format='%H %P %s' HEAD

An empty unmerged-stage listing proves index resolution; the policy test proves the chosen value satisfies the lab's operational constraint.

8. Engineer a rename/delete conflict

cd ..
git init -b trunk rename-delete
cd rename-delete
git config user.name "Merge Lab"
git config user.email "merge-lab@example.invalid"

printf "legacy=true\nowner=platform\n" > legacy.conf
git add legacy.conf
git commit -m "base"

git switch -c rename-side
git mv legacy.conf current.conf
printf "migrated=true\n" >> current.conf
git add current.conf
git commit -m "rename and migrate"

git switch trunk
git rm legacy.conf
git commit -m "retire legacy configuration"

git merge-tree --write-tree trunk rename-side
echo "preview exit=$?"
git merge --no-ff rename-side
echo "merge exit=$?"
git status --short
git ls-files -u

The decision is “migration survives” versus “endpoint retired,” not merely “ours versus theirs.” Preserve status/index messages before choosing. Then abort this diagnostic scenario:

git merge --abort
cd ..

9. Engineer a file/directory conflict

git init -b trunk df-conflict
cd df-conflict
git config user.name "Merge Lab"
git config user.email "merge-lab@example.invalid"

printf "base\n" > root.txt
git add root.txt
git commit -m "base"

git switch -c file-side
printf "single-file-docs\n" > docs
git add docs
git commit -m "add docs as file"

git switch trunk
mkdir docs
printf "directory-docs\n" > docs/readme.md
git add docs/readme.md
git commit -m "add docs directory"

git merge-tree --write-tree trunk file-side
echo "preview exit=$?"
git merge --no-ff file-side
echo "merge exit=$?"
git status --short
git ls-files -u
git merge --abort
cd ..

A path cannot be both a file and a directory. Read Git's informational messages and index state because this structural conflict is not reducible to one ordinary marker block.

10. Create a mechanically clean but semantically invalid merge

git init -b trunk semantic-conflict
cd semantic-conflict
git config user.name "Merge Lab"
git config user.email "merge-lab@example.invalid"

cat > pool.conf <<'EOF'
min_workers=2
# allocator tuning
queue_depth=100
batch_size=10
backoff_ms=50
health_window=30
# upper capacity
max_workers=8
EOF

cat > check_pool.sh <<'EOF'
#!/bin/sh
min=$(sed -n 's/^min_workers=//p' pool.conf)
max=$(sed -n 's/^max_workers=//p' pool.conf)
test "$min" -le "$max"
EOF

git add .
git commit -m "base: valid worker pool"

git switch -c high-min
sed -i 's/min_workers=2/min_workers=10/' pool.conf
git commit -am "feature: require ten workers"

git switch trunk
sed -i 's/max_workers=8/max_workers=6/' pool.conf
git commit -am "operations: cap workers at six"

git merge --no-ff high-min -m "merge high-min"
echo "Git merge exit=$?"
sh check_pool.sh
echo "domain test exit=$?"

Because the edited lines are well-separated, Git should merge them cleanly. The test fails because 10 > 6. This is a semantic conflict.

sed -i 's/max_workers=6/max_workers=12/' pool.conf
sh check_pool.sh
git add pool.conf
git commit -m "fix integration: restore worker invariant"
cd ..

11. Record one resolution with rerere

git init -b trunk rerere-lab
cd rerere-lab
git config user.name "Merge Lab"
git config user.email "merge-lab@example.invalid"
git config rerere.enabled true
git config rerere.autoUpdate false

printf "color=blue\n" > theme.conf
git add theme.conf
git commit -m "base"
BASE=$(git rev-parse HEAD)

git switch -c left-one
printf "color=green\n" > theme.conf
git commit -am "left green"

git switch -c right-one "$BASE"
printf "color=orange\n" > theme.conf
git commit -am "right orange"

git switch left-one
git merge --no-ff right-one || true
git rerere status
git rerere diff

printf "color=teal\n" > theme.conf
git add theme.conf
git rerere
git commit -m "merge: choose teal"

12. Recreate the same conflict and validate the remembered result

git switch -c left-two "$BASE"
printf "color=green\n" > theme.conf
git commit -am "left2 green"

git switch -c right-two "$BASE"
printf "color=orange\n" > theme.conf
git commit -am "right2 orange"

git switch left-two
git merge --no-ff right-two || true

cat theme.conf
git ls-files -u -- theme.conf
grep -qx 'color=teal' theme.conf

git add theme.conf
git ls-files -u -- theme.conf
git commit -m "merge: validated rerere reuse"

Rerere should place color=teal in the working tree. With autoupdate disabled, the unmerged index remains until explicit staging.

13. Challenge — select evidence from the state model

  1. Which command previews modern merge machinery without altering the live worktree/index?
  2. Which commands reveal the exact base/ours/theirs versions of a conflicted file?
  3. What proves a file is no longer unmerged at the index layer?
  4. What kind of conflict is a clean merge whose test fails?
  5. What must happen before accepting a rerere-reused resolution?

14. Cleanup

Every repository is disposable. Confirm the parent path before recursive deletion.
cd ../..
pwd
rm -rf git-merge-engineering-lab

PowerShell equivalent: Set-Location ../..; Remove-Item -Recurse -Force git-merge-engineering-lab.

15. Knowledge check

Question 1. Why did HEAD not move during the unresolved text merge?

Question 2. Why can rename/delete be missed by marker-only checks?

Question 3. What proves the semantic scenario was not a textual conflict?

Question 4. Why keep rerere.autoUpdate false in this lab?

Question 5. What does git ls-files -u show?

16. Summary

You previewed conflict computation, inspected index stages, aborted/retried, diagnosed structural conflicts, demonstrated a clean semantic failure, and proved rerere can reuse editing work without eliminating validation.

Next

Choose configuration and driver policy deliberately

Lesson 3 covers conflict styles, rerere staging policy, renormalization, generated files, lock files, and custom driver runtime boundaries.

Authoritative references

 git-merge
 git-merge-base
 git-merge-tree
 git-restore
 git-rerere

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.