Sharing rerere resolutions across a team Jump to heading

When a long-lived branch is rebased onto a fast-moving trunk, several people hit the same conflict in the same week and each resolves it independently β€” sometimes differently, which is worse than the wasted effort. Git’s recorded-resolution cache solves this per machine; sharing it solves it per team. The cache is also, viewed less charitably, a mechanism for applying one person’s judgement to other people’s merges without asking, which is why this recipe is as much about review as about copying files. It extends Reusing Conflict Resolutions with rerere.

When to use this approach Jump to heading

  • Several engineers rebase the same long-lived branch and hit identical conflicts.
  • A cross-cutting refactor on the trunk conflicts with every branch in flight in the same way.
  • One person has already worked out the correct resolution and it is genuinely mechanical.
  • Consistency matters: the same conflict resolved two ways produces a subtle behavioural difference.
  • If the conflict requires judgement that could reasonably differ per branch, do not share it. Let each person see the conflict.

Step 1 β€” Inspect what your rr-cache actually contains Jump to heading

# rerere must be recording in the first place
git config rerere.enabled true
git config rerere.autoUpdate true

# What has been recorded? Each directory is one conflict signature.
ls .git/rr-cache/
git rerere status              # conflicts recorded for the current operation
# Read a specific resolution before you consider sharing it
for d in .git/rr-cache/*/; do
  echo "=== $d"
  [ -f "$d/preimage" ]  && head -20 "$d/preimage"
  [ -f "$d/postimage" ] && echo "--- resolved to:" && head -20 "$d/postimage"
done

What changed: nothing β€” but you can now see that a cache entry is a pair of files, the conflicted text and what you turned it into, keyed by a hash of the conflict.

What one cache entry isA conflict is normalised and hashed to produce a directory name. Inside it, the preimage records the conflict as it appeared and the postimage records what you turned it into. A later conflict that hashes to the same value has its postimage applied automatically.first conflict<<<<<<< ours>>>>>>> theirsnormalise + hashrr-cache/4f2a…/branch noise strippedthe cache entrypreimage β€” the conflictpostimage β€” your resolutionthisimage β€” in-progress statesame conflict, lateranother branch, another dayhashes to 4f2a…an exact matchpostimage applied"Resolved … using previous resolution"

Step 2 β€” Export only the resolutions worth sharing Jump to heading

Do not share the whole cache. It contains every conflict you have ever resolved, including ones specific to your own half-finished experiments.

# Identify the entry for a conflict you want to share, by its content
grep -rl 'settlementJournal' .git/rr-cache/*/preimage

# Export just that entry, with a README explaining the decision
mkdir -p /tmp/rr-share/4f2a9c1b8e7d6a5c4b3a2918
cp .git/rr-cache/4f2a9c1b8e7d6a5c4b3a2918/preimage \
   .git/rr-cache/4f2a9c1b8e7d6a5c4b3a2918/postimage \
   /tmp/rr-share/4f2a9c1b8e7d6a5c4b3a2918/

cat > /tmp/rr-share/README <<'EOF'
Conflict: Ledger→Journal rename versus the settlement export branch.
Resolution: keep the renamed symbol, retain the branch's added guard clause.
Decided by: r.okafor, reviewed in PR #2841.
Expires: after the export branch merges β€” the conflict stops existing.
EOF

tar -czf rr-settlement-rename.tgz -C /tmp/rr-share .

What changed: a single, documented resolution is packaged for other people, with the reasoning that made it correct and a note about when it stops applying.

# Verify the archive holds only what you intended
tar -tzf rr-settlement-rename.tgz

SAFETY WARNING β€” a postimage is arbitrary file content that will be written into a colleague’s working tree and, with rerere.autoUpdate on, staged automatically. Anyone who can hand you a cache entry can put code into your commit without it appearing as a conflict. Only import archives from people you would give commit access to, read the postimage before importing, and diff the result of the first merge that uses it.

Step 3 β€” Import into another clone Jump to heading

# Extract into the recipient's cache
mkdir -p .git/rr-cache
tar -xzf rr-settlement-rename.tgz -C .git/rr-cache

# Read what you just accepted β€” before it is applied to anything
cat .git/rr-cache/4f2a9c1b8e7d6a5c4b3a2918/postimage
# Confirm it applies on the next occurrence
git rebase origin/main
# Expect: Resolved 'src/settlement/export.ts' using previous resolution.

# Then check what it did, rather than trusting it
git diff --cached src/settlement/export.ts

That last diff is not optional. An automatically applied resolution is the only kind of merge outcome that reaches your index without you having looked at the conflict, and a shared resolution is one you did not even make.

Four people, one conflictWithout sharing, four engineers each resolve the same conflict and two of them do it differently, producing inconsistent behaviour. With a reviewed shared resolution, all four merges produce the same result and the difference is caught once, in review, rather than four times in production.unshared β€” four independent judgementsengineer A β†’ v1engineer B β†’ v1engineer C β†’ v2engineer D β†’ v3three different behaviours in the trunk, discovered separately, over weeksshared β€” one reviewed judgementengineer A β†’ v1B, imported β†’ v1C, imported β†’ v1D, imported β†’ v1the decision was made once and reviewed once β€” which is the actual benefit, more than the time saved

Step 4 β€” Expire resolutions when the code moves on Jump to heading

A shared resolution is correct for a particular pair of changes. Once the branch merges or the code is rewritten, the entry is at best dead weight and at worst a resolution applied to a conflict that only superficially resembles the original.

# Drop a specific recorded resolution
git rerere forget src/settlement/export.ts

# Prune entries the cache has not matched recently
git config gc.rerereResolved 60      # days to keep resolved entries
git config gc.rerereUnresolved 15
git gc --prune=now
# Confirm the entry is gone and the conflict returns to being manual
ls .git/rr-cache/ | wc -l
git rebase origin/main    # the conflict now presents normally
A shared resolution has a shelf lifeWhile the conflicting branch is open the shared resolution saves repeated work. Once the branch merges, the conflict stops occurring and the entry is harmless dead weight. If the code is later rewritten, a superficially similar conflict can match the stale entry and be resolved wrongly without anyone seeing it.useful β€” the conflict recurs dailydead weighta liabilitybranch mergesthe code is rewrittenthe third band is the dangerous one: a superficially similar conflict matches the stale hashand is resolved automatically with a decision that no longer appliesthe stated expiry date is what moves the entry out before it reaches band three

The expiry note in the exported README is what makes this happen. Without a stated end condition, a shared cache entry outlives the situation that justified it, and the day it silently resolves a conflict that needed thought is the day the whole practice becomes a liability.

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Is sharing an rr-cache safe? Jump to heading

It is safe in the sense that it cannot corrupt a repository, and risky in the sense that it silently applies somebody else’s judgement to your merge. A recorded resolution is a decision about what two conflicting changes should become β€” importing one means adopting that decision without seeing the conflict. Share deliberately, review what you import, and never automate the import into every clone.

Why not just commit the rr-cache to the repository? Jump to heading

Because it would then be applied automatically on every clone with no review step, which turns an opt-in convenience into a silent policy. It also accumulates: entries whose conflicts stopped existing years ago stay forever, and nothing prompts anyone to remove them. Distribute it as an artefact people choose to import instead.

How do I know a shared resolution was applied? Jump to heading

Git prints Resolved '<path>' using previous resolution during the merge or rebase, and the file is staged if autoUpdate is on. Read that line β€” it is the only notice you get that a decision was made for you. Diff the result before continuing, particularly for a resolution you imported rather than recorded yourself.