Git Rerere

This is an optional advanced lesson for developers who repeatedly resolve the same conflict. Rerere means reuse recorded resolution. It remembers how you resolved a matching conflict before and can put that resolution into the working file when the conflict returns.

Why The Same Conflict Returns

Imagine a long-lived PHP invoice branch. It changes src/Invoice.php to round totals differently. Meanwhile, main changes the same lines to apply a discount. You merge main into the branch to test the combination, resolve the conflict, then later replay or repeat the integration as the branch evolves. Resolving identical conflict markers again wastes time.

How Rerere Actually Works

You resolve a conflict between two branches today. Later, after a rebase, another test merge, or an update to a long-running branch, essentially the same conflict appears again. Without rerere, you make the same resolution by hand. With rerere enabled, Git can remember your earlier resolution and try to reuse it.

  1. A merge or rebase produces a conflict.
  2. Git records the conflicted form of the file.
  3. You resolve the conflict manually.
  4. Git records how you resolved it.
  5. A related conflict appears later.
  6. Git recognises a previous resolution it may be able to reuse.
  7. Git attempts to apply that resolution to the working tree.
  8. You inspect and test the result.
  9. You stage it only when satisfied.
Conflict appears
      ↓
Resolve it manually
      ↓
Git remembers the resolution
      ↓
Similar conflict appears later
      ↓
Rerere reuses the recorded resolution
      ↓
Review → test → stage

Rerere does not understand what the PHP code is supposed to do. It remembers how a conflict was resolved previously and tries to reuse that information when a related conflict appears again. Learn to resolve normal merge conflicts first; rerere does not replace that skill.

Git calls the conflicted form it saw before your resolution the preimage, and the resolved form you produced the postimage. Given the earlier conflict, its resolution, and a new conflict, Git can try to construct a resolution for the new situation. This is more than blindly pasting old text: it may work even when surrounding history has changed. You must still review the result.

Git stores these records under .git/rr-cache/. This is local Git metadata: it is not a commit, is not automatically pushed to a remote, and does not create a shared team rule. Do not edit it manually.

Enable it for this repository before the first conflict:

git config rerere.enabled true
git config --get rerere.enabled

Once enabled, normal merges, commits, and rebases invoke rerere when appropriate. You do not normally need to run git rerere after each conflict. The explicit command below makes the recording step visible; its subcommands are also useful for inspection and control.

Try A Small Repeated Conflict

Use a disposable repository, not a production branch. main will apply a discount, while feature/invoice will round the same calculation. Their merge will conflict; you will resolve it once, then create a second branch that repeats the conflict so rerere can reuse your resolution.

mkdir rerere-demo
cd rerere-demo
git init -b main
git config user.name "Lesson Example"
git config user.email "lesson@example.test"
git config rerere.enabled true
mkdir src
printf '%s\n' '<?php' 'function invoiceTotal(float $subtotal): float {' '    return $subtotal;' '}' > src/Invoice.php
git add src/Invoice.php
git commit -m "Add invoice total"
git branch feature/invoice
printf '%s\n' '<?php' 'function invoiceTotal(float $subtotal): float {' '    return $subtotal * 0.9;' '}' > src/Invoice.php
git add src/Invoice.php
git commit -m "Apply discount on main"
git switch feature/invoice
printf '%s\n' '<?php' 'function invoiceTotal(float $subtotal): float {' '    return round($subtotal, 2);' '}' > src/Invoice.php
git add src/Invoice.php
git commit -m "Round invoice total on feature"
git merge main

The merge stops at a conflict. Inspect src/Invoice.php; the conflicting part will look like this:

PHP example
function invoiceTotal(float $subtotal): float
{
 <<<<<<< HEAD
    return round($subtotal, 2);
 =======
    return $subtotal * 0.9;
 >>>>>>> main
}

HEAD is the version on feature/invoice; the other side came from main. For this teaching example, combine both behaviours:

PHP example
function invoiceTotal(float $subtotal): float
{
    return round($subtotal * 0.9, 2);
}

This formula demonstrates Git behaviour, not a general rule for invoice arithmetic. git rerere status lists the path it is tracking, and git status --short shows the unmerged file. The next commands write the combined form and record it:

printf '%s\n' '<?php' 'function invoiceTotal(float $subtotal): float {' '    return round($subtotal * 0.9, 2);' '}' > src/Invoice.php
git rerere
git rerere diff
php -l src/Invoice.php
git diff --check
git add src/Invoice.php
git commit -m "Merge main into feature with discount and rounding"

git rerere explicitly records the resolved file here; normal Git operations also do this automatically. git rerere diff helps inspect how your resolution differs from the recorded conflict. Now recreate the same merge from the feature commit before that merge. HEAD^1 below is its first parent, the feature tip before merging main.

git switch -c feature/invoice-retry HEAD^1
git merge main
git status
cat src/Invoice.php
git rerere status
git rerere remaining

This is the payoff: the merge encounters the same underlying conflict, but rerere recognises its recorded resolution. Git should report reuse, and the working file should contain return round($subtotal * 0.9, 2);. git rerere remaining should not list this autoresolved path. Git has not checked whether the PHP behaviour is right.

By default, rerere writes a candidate resolution to the working tree and leaves the index unmerged. That gives you an explicit review point before staging. Inspect the file and diff, check for remaining conflict markers, run the syntax check and your project's focused tests, then stage and commit:

git status
git diff
php -l src/Invoice.php
git diff --check

# Run relevant tests here.

git add src/Invoice.php
git commit -m "Merge main with verified invoice resolution"

As an advanced option, git config rerere.autoupdate true lets a cleanly reused resolution update the index too. This is convenient but removes part of the explicit review step. Leave the default behaviour alone while learning, so you can inspect the result before staging.

If you are only practising, delete the disposable repository once you have inspected it. In real work, preserve any wanted commits first.

Inspect And Control Rerere

Command Purpose
git rerere status Show paths whose conflict resolutions rerere is tracking.
git rerere remaining Show conflicts not currently autoresolved by rerere.
git rerere diff Show how the current resolution differs from the recorded conflict.
git rerere forget <path> Forget the recorded resolution for the current conflict on that path, so you can resolve it afresh.
git rerere clear Clear rerere metadata for a current resolution you are abandoning; it does not delete every recorded resolution.
git rerere gc Prune old rerere records.

When Rerere Helps

Rerere is useful when you repeatedly rebase a long-running feature branch, merge main into it for testing, maintain a patch series against a changing upstream branch, or update stacked branches or pull requests with recurring conflicts. It helps much less with one-off conflicts or when the surrounding logic changes substantially each time.

Review Every Reused Resolution

A resolution being reusable does not mean it is still correct. Yesterday, both branches may have expected return round($subtotal * 0.9, 2);. Later changes might introduce tax calculation, currency-specific rounding, or a pricing service. Rerere could supply the old expression and produce syntactically valid PHP even though the business behaviour is now wrong.

After reuse:

  1. Inspect the diff.
  2. Look for remaining conflict markers.
  3. Run syntax or static checks.
  4. Run focused tests.
  5. Understand the resulting behaviour.
  6. Only then stage or continue the merge or rebase.

See the Git rerere manual for its commands and recording behaviour.

What You Should Be Able To Do

You should be able to:

  • explain what rerere stands for and why conflicts recur;
  • enable it and describe preimage and postimage;
  • resolve and record a conflict, then recognise and inspect reuse;
  • distinguish a working-tree candidate from a staged resolution;
  • forget an obsolete resolution; and
  • judge when rerere helps and why every reused result needs review and testing.

Practice

Reuse A Conflict Resolution Safely

You are maintaining a long-running PHP branch, feature/shipping. Both main and the feature branch have changed the same calculation in src/Shipping.php. You expect to meet the conflict more than once while updating the feature branch. Use a disposable repository or a safe training branch for the exercise.

  1. Enable rerere for the repository.
  2. Confirm that it is enabled.
  3. Merge main into feature/shipping and encounter the conflict.
  4. Inspect the conflicted src/Shipping.php file and check which path rerere is tracking.
  5. Resolve the conflict correctly yourself.
  6. Run a PHP syntax check and relevant tests.
  7. Stage and commit the resolution.
  8. Recreate or encounter the related conflict again.
  9. Observe whether rerere reuses the recorded resolution.
  10. Inspect the actual file, its diff, and any remaining conflicts before staging it.
  11. If the remembered resolution is no longer appropriate, forget it. If rerere has already replaced the conflict markers, recreate the conflicted form from the index before resolving the file manually again.
  12. Explain why rerere reuse alone does not prove that the code is correct.
Show solution
git config rerere.enabled true
git config --get rerere.enabled
git switch feature/shipping
git merge main

The first merge stops at a conflict. Inspect the file and what rerere is tracking:

git status
cat src/Shipping.php
git rerere status
git rerere diff

Edit src/Shipping.php to make the correct calculation for this application. Remove the conflict markers, check the result, then stage and commit it. The commit invokes rerere automatically, so a separate git rerere command is not normally needed.

php -l src/Shipping.php
git diff --check

# Run relevant tests.

git add src/Shipping.php
git commit

When a later merge or rebase produces the related conflict again, inspect the candidate resolution before accepting it:

git status
git rerere status
git rerere remaining
cat src/Shipping.php
git diff
php -l src/Shipping.php
git diff --check

# Run relevant tests.

If rerere reused the recorded resolution, check the actual contents of src/Shipping.php and its behaviour before staging. By default, rerere writes to the working tree but leaves the index for your approval:

git add src/Shipping.php
git commit

If that remembered resolution is obsolete, git rerere forget src/Shipping.php tells Git to forget the recorded resolution for the current conflict on that path. It does not necessarily put the original conflict markers back into the working file. If rerere has already applied the remembered resolution and you need to edit the conflict again, git checkout --conflict=merge src/Shipping.php recreates the conflicted form from the index:

git rerere forget src/Shipping.php
git checkout --conflict=merge src/Shipping.php

git status
git diff

# Resolve src/Shipping.php manually.

php -l src/Shipping.php
git diff --check

# Run relevant tests.

git add src/Shipping.php

Use the checkout command when the reused resolution has already replaced the conflict markers. Once the file is resolved manually, inspect and test the result before staging it.

Rerere remembers an earlier conflict resolution, not the purpose of the shipping calculation. New tax rules, rounding, or pricing code may make the old resolution wrong even when the file parses. Inspect the diff and behaviour, run focused tests, and stage only a resolution you understand.