You rebase a long-lived branch onto main. The first commit conflicts in config.yml; you resolve
it. The second commit touches the same lines and conflicts again — in exactly the same way. So
does the third. Or you resolve a merge, abandon it, and a week later merge again and face the
identical hunk. Each time you re-read both sides, re-make the same decision, and re-type the same
resolution.
Git can remember it for you. git rerere — reuse recorded resolution — records how you resolved
a conflict and, when the same conflict appears again, writes that resolution into the file for
you. It is one configuration setting, and it comes with two rules that matter more than the
setting: a replayed resolution is a suggestion you must read, and it stops being right the moment
the code around it changes.
This article builds a repository where the same conflict appears twice, records a resolution on the first pass, and watches what Git does on the second. Everything runs in a disposable directory.
What rerere records
Section titled “What rerere records”When a merge, rebase, cherry-pick or revert leaves a conflicted file, rerere stores a normalised
copy of the conflicted hunk — the preimage: both sides of the conflict with the branch labels
stripped, so the record is about the content that clashed, not the names on the markers. When
you resolve the file and the resolution is committed (or the file is staged, depending on
configuration), rerere stores the resolved hunk — the postimage — alongside it, under
.git/rr-cache/<hash>/.
The next time a conflict produces the same preimage, rerere applies the postimage. Same content on both sides in, same resolution out. Different content on either side, no match, and you get an ordinary conflict to resolve by hand.
Three consequences follow, and the rest of the article demonstrates each:
- It is keyed on the conflict, not the operation — a resolution recorded during a merge replays during a rebase.
- By default it replays into the working tree only and leaves the file unmerged, so you must
inspect and
git addit.rerere.autoupdatechanges that. - A recorded resolution can be wrong, or become wrong. You can forget it.
Enabling it in a disposable repository
Section titled “Enabling it in a disposable repository”Everything below runs in a temporary directory with its own HOME, so no global configuration is
read or written and no existing repository is touched. The fixture script in the site repository,
scripts/articles/rerere-demo.sh, does exactly this; the commands are shown here so you can follow
along by hand.
WORK=$(mktemp -d)export HOME="$WORK" GIT_CONFIG_NOSYSTEM=1 GIT_EDITOR=truecd "$WORK"git init -q -b main demo && cd demogit config user.email lab@example.comgit config user.name "Lab User"
printf 'timeout: 3000\nretries: 3\n' > config.ymlgit add . && git commit -q -m "Add config"
git switch -qc featureprintf 'timeout: 500\nretries: 3\n' > config.ymlgit commit -qam "Fail fast: lower timeout"printf 'timeout: 500\nretries: 3\nbackoff: exponential\n' > config.ymlgit commit -qam "Add backoff"
git switch -q mainprintf 'timeout: 10000\nretries: 3\n' > config.ymlgit commit -qam "Raise timeout for slow networks"git log --oneline --all --graphCaptured output:
* b5957c2 Add backoff* c1359f5 Fail fast: lower timeout| * 952f545 Raise timeout for slow networks|/* d64a592 Add configTwo branches, both changed the timeout line, in opposite directions. Now enable rerere for
this repository only — git config without --global writes to .git/config:
git config rerere.enabled truegit config rerere.enabledtrueThe first conflict, resolved by hand
Section titled “The first conflict, resolved by hand”Merge main into feature:
git switch -q featuregit merge mainCaptured output:
Auto-merging config.ymlCONFLICT (content): Merge conflict in config.ymlRecorded preimage for 'config.yml'Automatic merge failed; fix conflicts and then commit the result.The third line is rerere at work: it has already stored the preimage. git rerere status lists
the paths it is tracking in the current operation:
git rerere statusconfig.ymlThe file has the usual markers:
<<<<<<< HEADtimeout: 500=======timeout: 10000>>>>>>> mainretries: 3backoff: exponentialResolve it — deliberately with a third value, so the resolution is clearly a decision and not “take one side”:
printf 'timeout: 5000\nretries: 3\nbackoff: exponential\n' > config.ymlgit add config.ymlgit commit -m "Merge main into feature: settle on 5000"Captured output:
Recorded resolution for 'config.yml'.[feature d4ea953] Merge main into feature: settle on 5000Recorded resolution is the moment the postimage was written. The cache now has both halves:
ls .git/rr-cache/*/postimagepreimageThe repeated conflict
Section titled “The repeated conflict”Now pretend the merge was the wrong approach — the team wants a rebase instead. Undo the merge commit and put the branch back exactly as it was:
git reset -q --hard ORIG_HEADgit log --oneline -1b5957c2 Add backoffPredict before you run it: what happens on git rebase main?
The first commit on feature changes timeout from 3000 to 500; main changed it from 3000 to
10000. That is the same clash as before — same two sides. Make a call on each of these before
reading on:
- Does Git report a conflict at all?
- What is in
config.ymlwhen the rebase stops — markers, or a resolved line? - Is the file staged?
- Does the rebase continue on its own?
git rebase mainCaptured output:
Rebasing (1/2)Auto-merging config.ymlCONFLICT (content): Merge conflict in config.ymlerror: could not apply c1359f5... Fail fast: lower timeouthint: Resolve all conflicts manually, mark them as resolved withhint: "git add/rm <conflicted_files>", then run "git rebase --continue".hint: You can instead skip this commit: run "git rebase --skip".hint: To abort and get back to the state before "git rebase", run "git rebase --abort".Resolved 'config.yml' using previous resolution.Could not apply c1359f5... Fail fast: lower timeoutThe answers: (1) yes, Git still reports CONFLICT and stops — rerere does not hide the conflict;
(2) the file already contains the resolution; (3) it is not staged; (4) the rebase does not
continue. Look at the file and the index:
cat config.ymlgit status --shorttimeout: 5000retries: 3UU config.ymlUU — unmerged — even though there are no markers. That is the default and it is the right
default: the resolution was written for you, but you have not said it is correct.
Inspecting a reused resolution before accepting it
Section titled “Inspecting a reused resolution before accepting it”Two views. git diff during a conflict shows the combined diff — what the resolved file looks
like relative to each parent:
git diffdiff --cc config.ymlindex 934c433,4a5691a..0000000--- a/config.yml+++ b/config.yml@@@ -1,2 -1,2 +1,2 @@@- timeout: 10000 -timeout: 500++timeout: 5000 retries: 3Read the two leading columns: - means the line came from the first parent only, - from the
second only, ++ is in neither — it is the resolution. That single ++ line is exactly what you
must check: is 5000 still the right answer for this rebase? Here it is. Accept it:
git add config.ymlgit rebase --continuegit log --onelinecat config.yml[detached HEAD 78cc3a6] Fail fast: lower timeout 1 file changed, 1 insertion(+), 1 deletion(-)Successfully rebased and updated refs/heads/feature.8fc3328 Add backoff78cc3a6 Fail fast: lower timeout952f545 Raise timeout for slow networksd64a592 Add configtimeout: 5000retries: 3backoff: exponentialThe second commit, “Add backoff”, applied without conflict — it only adds a line. A rebase of a
ten-commit branch where three commits touch the same hunk is where rerere earns its keep: the
same replay, three times, each one a git diff and a git add instead of a fresh decision.
git rerere diff is the other view: the difference between the recorded preimage and what is in
the working tree now. It is most useful while you are still editing, to see how your resolution
differs from the raw conflict.
Staging and rerere.autoupdate
Section titled “Staging and rerere.autoupdate”Why is the file left unstaged? Because staging is your signature on the resolution. rerere is confident the conflict matches; it cannot know whether the decision still applies.
If you trust your recorded resolutions — typically on a branch you rebase repeatedly against the
same base — rerere.autoupdate stages the replayed file as well:
git reset -q --hard ORIG_HEADgit config rerere.autoupdate truegit rebase mainCaptured output (the conflict lines are identical to before; the difference is one word):
CONFLICT (content): Merge conflict in config.ymlStaged 'config.yml' using previous resolution.git status --shortM config.ymlM in the first column: staged. The rebase still stops — rerere never continues an operation on
its own — but git rebase --continue now proceeds without a manual git add. On a branch with a
dozen identical conflicts that is a dozen stops and a dozen --continues; each stop is a chance to
notice that something looks different, which is why the stop is not optional.
Turn autoupdate off again for the rest of the walkthrough: git config --unset rerere.autoupdate.
Correcting a recorded resolution
Section titled “Correcting a recorded resolution”Suppose 5000 was wrong — the team has since decided main’s 10000 must win. The recorded
resolution will keep reinserting 5000. Discard it, during the conflict, with git rerere forget:
git reset -q --hard ORIG_HEADgit rebase maingit rerere forget config.ymlCaptured output (rebase lines omitted):
Resolved 'config.yml' using previous resolution.Updated preimage for 'config.yml'Forgot resolution for 'config.yml'forget removes the postimage. It does not put the conflict markers back in the working tree —
the file still contains the replayed 5000. To resolve afresh, ask Git to re-create the conflicted
version of the file from the index:
git checkout --conflict=merge config.ymlcat config.ymlRecreated 1 merge conflict<<<<<<< ourstimeout: 10000=======timeout: 500>>>>>>> theirsretries: 3Now resolve it the new way, git add, and continue; the new resolution is recorded in place of the
old one. (This walkthrough aborts the rebase instead: git rebase --abort.)
Two related commands, for completeness: git rerere clear discards the metadata for the
in-progress operation (what rerere would record if you committed now) without touching stored
resolutions, and git rerere gc prunes old entries — unresolved preimages after 15 days and
resolved ones after 60 by default (gc.rerereUnresolved, gc.rerereResolved).
Limitations, and when a human must look again
Section titled “Limitations, and when a human must look again”A different conflict is a different conflict. Change either side and nothing is replayed. Add a
commit on main that also changes retries, then merge again:
git switch -q mainprintf 'timeout: 10000\nretries: 5\n' > config.ymlgit commit -qam "More retries"git switch -q featuregit merge mainAuto-merging config.ymlCONFLICT (content): Merge conflict in config.ymlRecorded preimage for 'config.yml'Automatic merge failed; fix conflicts and then commit the result.No “using previous resolution”: the hunk now spans two lines on main’s side, so the preimage is
new. That is the safety property — rerere only speaks when the conflict is byte-for-byte the one it
saw before.
The same conflict can still deserve a different answer. The preimage matches on the conflicted
lines, not on the surrounding file, the tests, or the reason the code exists. If a function’s
contract changed between the first resolution and the replay, the replayed lines can be
syntactically perfect and semantically wrong. This is why the default leaves the file unstaged, and
why “same conflict” is not the same as “same correct resolution”. Read the ++ lines every time.
It is local. .git/rr-cache is not pushed, cloned or shared. A colleague rebasing the same
branch resolves the same conflicts by hand. Some teams script git rerere training on a shared
history (contrib/rerere-train.sh in Git’s source) to seed the cache; that is a deliberate,
reviewed step, not a default.
It records what you committed, including mistakes. A resolution that leaves a stray marker or
a wrong value is recorded just as faithfully. Catch it with the same habit that catches ordinary
conflict mistakes: a marker grep and the test suite before every --continue.
grep -nE '^(<<<<<<<|=======|>>>>>>>)' config.yml && echo "MARKERS REMAIN" || echo "clean"Common mistakes
Section titled “Common mistakes”- Expecting rerere to finish the rebase. It resolves the file; you continue the operation. With autoupdate it also stages; it still never continues.
- Enabling it after the first conflict and expecting the second to replay. The preimage is recorded when the conflict occurs. Enable it before the operation that first conflicts.
- Treating “Resolved using previous resolution” as “verified”. It means “matched”. Inspect
with
git diffbeforegit add. - Forgetting with the wrong tool.
git rerere cleardoes not remove a recorded resolution;git rerere forget <path>does, andgit checkout --conflict=merge <path>brings the markers back to resolve again. - Assuming it is shared. It is per-clone.
Try it: the conflict labs
Section titled “Try it: the conflict labs”rerere is only useful once resolving a conflict by hand is routine — the replayed content is a resolution you must be able to judge in seconds. Two labs build that, in a disposable repository with real output:
The lesson behind both, including the mental model for reading markers, is Resolving merge conflicts in Git; the rebase side is When not to rebase, which explains why repeated rebases of a shared branch — the very situation that makes rerere attractive — are usually the thing to avoid.
How this article was verified
Every command was run on 17 September 2026 in a freshly created temporary repository with an isolatedHOME, a throwaway identity and hooks disabled — never against a real repository. Output blocks labelled captured are pasted from that run; blocks labelled illustrativeare described rather than pasted. Versions: git 2.43.0 on Ubuntu 24.04. The fixture script is scripts/articles/rerere-demo.sh in the site repository; it rebuilds the example from scratch. Primary reference: git-scm.com/docs/git-rerere.
How did this go?
Unexpected output. Compare your output with the expected block line by line; the first differing line is usually the cause. Then check the version-sensitive notes.
Missing prerequisite. Most "stuck" moments here come from one earlier concept. The prerequisites list names it; the learning path puts it in order.
Unclear explanation. Read the explanation section, then the mental model, then retry the step — the command usually makes sense once the model is in place.
Instructions did not match my environment. Check `git --version` and your shell (Git Bash vs PowerShell vs zsh). Platform notes on the page name the differences that matter; the verification step confirms you are back to a known state.
Good. The next step is to do it without the page open — or move to the recommended next activity below.
All help on this page: Git troubleshooting decision tree · Find the lesson that comes before this one · How Git works — the model behind the commands · Recover a lost commit (lab)