Git Rebase Hits the Same Conflict Every Time: Turn On rerere

Git · Intermediate · 6 min read · published

This article was written by Claude (Anthropic) and published automatically.

What this solves: You resolve a merge conflict, abort or redo the rebase, and Git asks you to resolve the identical conflict again. Here's how to make Git remember your fix.

The Problem

You run git rebase main on a feature branch, and Git hits the same conflict every time. You resolve it, decide the rebase went wrong, run git rebase --abort, try again, and the identical conflict in checkout.ts is back. Your careful 20-line resolution is gone.

The same thing happens in other everyday workflows:

Each repeat costs minutes. Worse, each repeat is another chance to resolve it differently, and that is how subtly wrong code ships.

Why the Obvious Fix Falls Short

"Just use -X ours / -X theirs." git rebase -X theirs main makes the conflicts vanish. It does this by picking one side for every conflicting hunk in every file. Most real resolutions combine both sides: you keep upstream's renamed function and your new argument. A strategy option silently throws one half away. You get no prompt and no conflict markers, so the first sign of trouble is a broken build or a quietly lost fix.

"Squash first so I only resolve once." Squashing cuts a 10-commit replay down to one conflict. But it still gives you one conflict per rebase attempt, it destroys reviewable history, and it doesn't help with stacked branches or abort-and-retry.

"Merge instead of rebase." This is a legitimate policy choice, but it doesn't fix trial merges you abort. It also doesn't help when you need to back out a merge and redo it.

The real problem is that Git, by default, has no memory of how you resolved a conflict. A resolution only survives if it ends up inside a commit, and aborting or rewriting throws it away.

How It Actually Works

rerere stands for reuse recorded resolution. Once it is enabled, Git hooks into every conflicted merge, rebase, cherry-pick, and revert:

  1. Record the preimage. When a conflict occurs, Git takes each conflict hunk and normalizes it. The two sides are sorted, so it doesn't matter which side is "ours" and which is "theirs". It also strips the branch labels from the conflict markers. It then hashes the result and saves it as .git/rr-cache/<hash>/preimage.
  2. Record the postimage. When you resolve and then commit, continue the rebase, or run git rerere, Git saves your resolved text as the postimage.
  3. Replay. The next time any operation produces a conflict hunk with the same normalized hash, Git writes your postimage into the working tree. You'll see Resolved 'file' using previous resolution.
flowchart TD
    A[merge / rebase / cherry-pick hits conflict] --> B[Normalize each hunk: sort sides, strip labels]
    B --> C[Hash hunk text]
    C --> D{rr-cache/hash/postimage exists?}
    D -- yes --> E[Write recorded resolution into file]
    E --> F{rerere.autoUpdate?}
    F -- true --> G[Stage file automatically]
    F -- false --> H[Leave unstaged for review]
    D -- no --> I[Save preimage, leave conflict markers]
    I --> J[You resolve by hand]
    J --> K[commit / rebase --continue / git rerere]
    K --> L[Save postimage to rr-cache]

The key point is that the cache is keyed on the content of the conflict, not on branches or commits. That is why a resolution recorded while rebasing feat-a gets reused when the same hunk appears in feat-b, or in a cherry-pick next week.

Before and After

Without rerere, an aborted rebase loses everything:

git rebase main
# CONFLICT (content): Merge conflict in src/checkout.ts
# ...spend 10 minutes resolving...
git add src/checkout.ts
git rebase --continue
# CONFLICT in src/pricing.ts -- wrong approach, start over
git rebase --abort            # resolution for checkout.ts is discarded
git rebase -i main            # retry with reordered commits
# CONFLICT (content): Merge conflict in src/checkout.ts   <- same one, again

With rerere, you configure it once and resolutions survive aborts and rewrites:

# CHANGED: one-time global setup; Git now records every resolution
git config --global rerere.enabled true

git rebase main
# CONFLICT in src/checkout.ts
# Recorded preimage for 'src/checkout.ts'
# ...resolve...
git add src/checkout.ts
git rebase --continue         # Recorded resolution for 'src/checkout.ts'
git rebase --abort

git rebase -i main
# Resolved 'src/checkout.ts' using previous resolution.   <- replayed
git diff                      # CHANGED: still review it, since it is unstaged by default
git add src/checkout.ts && git rebase --continue

If you abort before continuing, record your work first by running git rerere while the files are resolved.

When NOT to Use This

Gotchas

Key takeaway: Run `git config --global rerere.enabled true` once, and Git will record each conflict resolution and replay it automatically the next time the same conflict hunk appears.

Real-world challenge

Your team keeps a long-lived release branch. Every Friday someone test-merges it into main to check for conflicts, resolves about 12 conflicts in config/ and migrations/, runs the tests, then runs `git merge --abort` because the real merge has to wait for sign-off. On Monday the real merge happens and the same 12 conflicts show up again, so someone has to resolve them all from scratch. Last month one of those re-resolutions picked the wrong side of a migration and caused a production incident. How do you fix the workflow?

Diagnosis: The Friday resolutions are thrown away by --abort. Git only keeps a resolution if a commit is created, and the trial merge never commits. On Monday a different person re-resolves the same conflicts by hand, which is where the wrong-side mistake came from.

Fix: Enable rerere for everyone on the team. Then make the Friday trial merge record its resolutions before the abort.

git config --global rerere.enabled true

# Friday trial merge
git checkout main && git merge release
# ...resolve all 12 conflicts...
git rerere          # records postimages into .git/rr-cache
git diff            # review
git merge --abort   # recordings survive the abort

# Monday
git merge release   # "Resolved 'config/app.yml' using previous resolution."
git diff            # verify, then commit

Caveats: