Reusing Conflict Resolutions with rerere Jump to heading
Gitβs rerere β short for reuse recorded resolution β is the built-in machine that watches how you resolve a merge conflict, fingerprints the conflict, and silently re-applies your resolution the next time the identical conflict reappears. For anyone who rebases a long-lived branch repeatedly, maintains parallel release lines, or re-runs the same painful merge across a stack of commits, rerere converts a recurring manual chore into a one-time decision. This guide sits inside the broader Conflict Resolution & Safe Merge Operations discipline and assumes you already understand how a merge produces conflict markers; if you do not, start with 3-Way Merge Fundamentals and return here to automate the repetition.
Prerequisites Jump to heading
Before enabling rerere, verify your environment and mental model:
What rerere Actually Does Jump to heading
When a merge or rebase hits a conflict and rerere is enabled, Git records two snapshots of each conflicted file:
- The preimage β the file with conflict markers, normalized so that incidental differences (branch names in the markers, surrounding line numbers) do not affect the fingerprint. This is what
rererematches against. - The postimage β the resolved file after you edit it and stage the result. This is what
rererereplays.
Both are stored under .git/rr-cache/<hash>/, where <hash> is a stable fingerprint of the normalized preimage. The next time Git produces a conflict whose normalized preimage hashes to the same value, rerere looks up the cached postimage and writes it straight into your working tree. You did the thinking once; Git does the typing forever after.
The diagram below shows the record-and-replay lifecycle: the first conflict is resolved by hand and captured into the cache, and every later occurrence of the same conflict shape is auto-resolved from that cache.
The key mental model: rerere matches on the shape of the conflict, not on file paths, commit SHAs, or branch names. Two completely different files that produce a byte-identical conflict hunk will share one cache entry. This is exactly what makes it powerful across rebases β and exactly what makes a wrong entry dangerous, which the safety section below covers in detail.
Step 1 β Enable rerere and autoUpdate Jump to heading
Intent: Turn on recording and replay, and let Git stage replayed resolutions automatically.
# Enable recording and replay for the current user (all repos)
git config --global rerere.enabled true
# Automatically stage a replayed resolution into the index
git config --global rerere.autoUpdate true rerere.enabled is the master switch. rerere.autoUpdate is the convenience layer: with it on, an auto-resolved file is added to the index for you, so git status shows it as staged rather than as an unresolved conflict you still have to git add. Leave autoUpdate off if you prefer to eyeball every replayed resolution before staging it β a reasonable stance on a security-sensitive codebase.
Verify: Confirm both settings are live:
git config --get rerere.enabled # β true
git config --get rerere.autoUpdate # β true You can scope these per-repository instead of globally by dropping --global. A repo-local setting in .git/config overrides the global one, which is useful when only certain repositories have recurring conflicts worth caching.
Step 2 β Record a Resolution on the First Conflict Jump to heading
Intent: Resolve a conflict once, by hand, and confirm rerere captured it.
Trigger a real conflict β for example, merging a branch that edits the same lines as main:
git merge feature/pricing-refactor
# Auto-merging src/pricing.js
# CONFLICT (content): Merge conflict in src/pricing.js Immediately inspect what rerere is tracking:
git rerere status
# β src/pricing.js git rerere status lists the files for which rerere has recorded a preimage in this operation. Now resolve the conflict the way you normally would β edit the file, remove the markers, and stage it:
# Edit src/pricing.js to the correct merged content, then:
git add src/pricing.js Staging the resolved file is the moment rerere captures the postimage. Confirm the entry now exists in the cache:
git rerere diff # Shows preimage β your resolution for active conflicts
ls .git/rr-cache/ # One directory per recorded conflict fingerprint Verify: git rerere diff prints a unified diff from the recorded conflict to your resolution. If it shows your intended edit, the resolution is recorded and ready to replay. Complete the merge with git commit as usual β rerere does not commit for you.
Step 3 β Replay the Recorded Resolution Automatically Jump to heading
Intent: Prove that the same conflict now resolves itself.
Abort and re-run the same merge (or hit the conflict again later) to see replay in action:
git merge --abort
git merge feature/pricing-refactor
# Auto-merging src/pricing.js
# CONFLICT (content): Merge conflict in src/pricing.js
# Resolved 'src/pricing.js' using previous resolution. That final line β Resolved '...' using previous resolution. β is rerere replaying the cached postimage. The working-tree file is already resolved. With rerere.autoUpdate true, it is also already staged:
git status
# On branch main
# Changes to be committed:
# modified: src/pricing.js With autoUpdate off, the file content is correct in the working tree but still shows as unmerged, and you finish with an explicit git add.
Verify: Diff the working tree against the resolution you recorded β they must be identical, and there must be no leftover conflict markers:
git diff --staged src/pricing.js # Matches your Step 2 resolution
grep -n '<<<<<<<\|>>>>>>>' src/pricing.js || echo "no markers β clean" Always read the replayed result before committing. rerere is fast and confident, and confidence is precisely the failure mode to guard against β see the safety warning below.
Step 4 β Use rerere During Long-Lived Rebases Jump to heading
Intent: Eliminate the biggest repeated-conflict source: rebasing a stack of commits, where the same conflict resurfaces on every commit that touches the contested lines.
This is where rerere earns its keep. During an interactive rebase, each replayed commit that touches the conflicting region re-raises the same conflict. Without rerere you resolve it N times; with it, you resolve it once and Git replays the rest. Pair it with the techniques in Interactive Rebase Workflows:
# Rebase a long-running branch onto the updated trunk
git rebase -i origin/main
# On the first conflicting commit:
# ... resolve src/pricing.js by hand, then:
git add src/pricing.js
git rebase --continue
# On every subsequent commit that reintroduces the same conflict:
# "Resolved 'src/pricing.js' using previous resolution."
git rebase --continue # autoUpdate already staged it Turn on rerere.autoUpdate before a big rebase so that each auto-resolved step needs only git rebase --continue rather than a manual git add. For a genuinely long rebase, also enable rebase auto-stash and fixup handling so unrelated friction does not interrupt the flow:
git config rebase.autoStash true # Stash a dirty tree instead of refusing to rebase
git config rebase.autoSquash true # Honour fixup!/squash! prefixes automatically Verify: After the rebase completes, confirm no conflict markers survived into any commit in the rebased range:
git rebase --continue # until "Successfully rebased"
git log -p origin/main..HEAD | grep -n '<<<<<<<' || echo "clean history" The same recorded resolution also helps when you cherry-pick the same fix across release branches β if the backport hits an identical conflict shape on each branch, rerere replays it every time.
Step 5 β Share the rr-cache Across a Team Jump to heading
Intent: Let a team reuse one carefully-reviewed resolution of a gnarly recurring merge, rather than each engineer solving it independently (and inconsistently).
The cache lives in .git/rr-cache/ and is not pushed with your commits β rerere is local by default. There are two supportable ways to share it.
Option A β Point every clone at a shared directory. Replace the per-repo cache with a symlink to a synced location (a shared network path, or a directory kept in sync by your dotfiles tooling):
# Move the existing cache aside and link to a shared, synced location
mv .git/rr-cache .git/rr-cache.local.bak
ln -s "$HOME/shared/rr-cache" .git/rr-cache
# Now every recorded resolution is read from and written to the shared cache Option B β Distribute specific resolutions as reviewed artifacts. Export the entries you trust from a lead engineerβs clone and hand them to teammates, so only approved resolutions propagate:
# On the source clone: archive the vetted cache entries
tar -czf rr-cache-pricing.tgz -C .git rr-cache
# On a teammate's clone: unpack alongside the existing cache
tar -xzf rr-cache-pricing.tgz -C .git # merges entries into .git/rr-cache/ Because rr-cache is keyed by conflict fingerprint, an entry recorded on one machine replays on any other machine that meets the same conflict β no path or SHA coupling required.
Verify: On the receiving clone, reproduce the shared conflict and confirm the replay message appears:
git merge feature/pricing-refactor
# β Resolved 'src/pricing.js' using previous resolution.
git rerere diff # confirm the replayed resolution matches the team's intent SAFETY WARNING: A shared
rr-cachepropagates a wrong resolution to everyone who consumes it β silently, with no conflict shown β which can corrupt merges across the whole team. Treat every shared entry as a reviewed code artifact: verify it withgit rerere diffbefore distributing, and remove a suspect entry everywhere withgit rerere forget <path>on each clone before it does more damage.
Step 6 β Forget a Bad Recorded Resolution Jump to heading
Intent: Recover when rerere keeps replaying a resolution that turned out to be wrong.
This is the single most important recovery command in the whole feature. If you resolved a conflict incorrectly the first time, rerere will faithfully re-apply that mistake every time the conflict recurs β quietly, because it reports success, not a conflict. To fix it, you must delete the recorded resolution while the conflict is present:
# Re-trigger the conflict so the bad preimage is active in the working tree
git merge feature/pricing-refactor
# rerere silently replays the WRONG resolution here
# Discard the recorded resolution for this specific conflict
git rerere forget src/pricing.js
# β Updated preimage for 'src/pricing.js' git rerere forget <path> deletes the cached entry and restores the raw conflict markers in that file, handing control back to you. Resolve it correctly and stage it β the corrected postimage overwrites the bad one:
# Edit src/pricing.js correctly this time, then:
git add src/pricing.js # records the corrected resolution in place of the bad one If you want to wipe all recorded resolutions and start clean β for instance after a large refactor invalidates your cache β clear the whole directory:
rm -rf .git/rr-cache/* # Local cache only; safe, non-destructive to history Verify: After forgetting, confirm the conflict reappears with genuine markers rather than being silently auto-resolved:
git merge --abort
git merge feature/pricing-refactor
# Should now show a real CONFLICT again, NOT "using previous resolution"
grep -n '<<<<<<<' src/pricing.js && echo "markers restored β resolve correctly now" SAFETY WARNING: Clearing
.git/rr-cacheor runninggit rerere forgetonly discards recorded resolutions β it never touches commits, refs, or working-tree history β so it is always safe to run. The genuine danger is the opposite: a stale cache silently reapplying a resolution that no longer fits the current code. When a replayed merge looks wrong, rungit rerere forget <path>immediately and re-resolve by hand rather than committing the auto-resolved result.
Integration with Adjacent Workflows Jump to heading
rerere is a force multiplier for the other conflict-handling tools in this section β it does not replace any of them, it removes the repetition from all of them.
Interactive rebase is the highest-value pairing. A rebase that replays a long stack of commits over a moved trunk re-raises the same conflict on each commit; the patterns in Interactive Rebase Workflows plus rerere.autoUpdate reduce an N-conflict rebase to a single manual resolution followed by repeated git rebase --continue.
Understanding what you are resolving remains a prerequisite, not an optional extra. rerere records whatever resolution you give it β including a bad one β so the accuracy of your first manual resolution is everything. The three-way reasoning covered in 3-Way Merge Fundamentals is what makes that first resolution correct, and therefore what makes every replay correct.
Backporting across release lines is the other natural fit. When you cherry-pick a fix across multiple release branches and each branch presents the same conflict against the patch, rerere replays your resolution on every branch, keeping the backport consistent instead of subtly divergent.
The boundary of responsibility is clear: rerere handles repetition, the operator handles correctness. Never let the convenience of automatic replay substitute for reading the merged result before you commit it.
Troubleshooting Jump to heading
| Symptom | Likely cause | Fix |
|---|---|---|
No Resolved ... using previous resolution message ever appears | rerere.enabled is not set, or was set after the conflict was first recorded | Run git config rerere.enabled true, then re-resolve the conflict once so a preimage is recorded |
Replayed file is still marked unmerged, needs manual git add | rerere.autoUpdate is off | Set git config rerere.autoUpdate true, or just git add the auto-resolved file yourself |
rerere keeps applying an obviously wrong resolution | A bad postimage is cached for that conflict fingerprint | Trigger the conflict, run git rerere forget <path>, re-resolve correctly, and stage |
| Expected replay does not happen on a similar-looking conflict | The conflict shape differs (different surrounding context or sides), so the fingerprint differs | This is by design β resolve it once and rerere will record the new shape too |
git rerere status shows nothing during an active conflict | The conflict was resolved and staged already, or rerere is disabled | Confirm rerere.enabled true; a resolved conflict has no active preimage to list |
| Shared cache entry produces a wrong merge for teammates | A bad resolution was distributed via rr-cache | Run git rerere forget <path> on every clone, re-record correctly, redistribute the vetted entry |
| Cache grows large over time | rerere retains resolutions and preimages for a configured age | Run git rerere gc (respects gc.rerereResolved / gc.rerereUnresolved day thresholds) to prune old entries |
Frequently Asked Questions Jump to heading
Does git rerere change my history or commits? Jump to heading
No. rerere only re-applies the working-tree edits that resolve a conflict. It writes resolved file content into your index during a merge or rebase, but it never creates commits, moves refs, or rewrites history on its own. You still stage and commit exactly as you would by hand β rerere just spares you retyping the resolution.
What is the difference between rerere.enabled and rerere.autoUpdate? Jump to heading
rerere.enabled turns on recording and replay: Git remembers how you resolved a conflict and re-applies the same resolution the next time the identical conflict appears. rerere.autoUpdate additionally stages the replayed resolution into the index automatically, so you do not have to run git add on the auto-resolved files. Without autoUpdate, rerere resolves the file in the working tree but leaves staging β and therefore a final human glance β to you.
How does rerere decide two conflicts are the same? Jump to heading
rerere computes a normalized hash of the conflict itself β the surrounding context and the two conflicting sides between the merge markers, with variable content abstracted away. If a later conflict produces the same normalized preimage, rerere treats it as identical and replays the recorded postimage. It matches on conflict shape, not on file path or commit identity, which is why one recorded entry can resolve the same conflict across many commits in a rebase.
Can I share a rerere cache with my whole team? Jump to heading
Yes, but do it deliberately. The cache lives in .git/rr-cache, which is not pushed by default. You can point rerere at a shared directory via a symlink, or distribute a lead engineerβs vetted entries as reviewed artifacts. Treat shared resolutions as reviewed code: a wrong entry propagates a wrong merge to everyone who consumes it, silently and with no conflict shown.
How do I undo a resolution that rerere keeps applying incorrectly? Jump to heading
Run git rerere forget <path> while the conflict is present in your working tree. This deletes the recorded resolution for that specific conflict and restores the raw conflict markers so you can resolve it correctly. The corrected resolution is recorded in place of the bad one when you next stage the file. Nothing in your commit history is affected β only the local resolution cache.
Related Jump to heading
- Automating Repeated Conflict Resolution with rerere β a hands-on walkthrough of scripting and CI-integrating rerere to eliminate recurring conflicts across a rebase-heavy team
- Conflict Resolution & Safe Merge Operations β the parent discipline covering every safe-merge technique this page builds on
- Interactive Rebase Workflows β the workflow where rerere pays off most, replaying one resolution across a long stack of commits
- 3-Way Merge Fundamentals β how a three-way merge produces conflicts, so your first (recorded) resolution is correct
- Cherry-Pick & Backporting β applying the same fix across release branches, where rerere replays the identical conflict on each line