跳到正文
DEV Community· Gabriel Anhaia·· 2 小時前AI 評分73

兩個編碼 Agent 修改同一檔案時,Git rerere 如何重用衝突解決結果

Two Coding Agents Edited the Same File. Make Git Resolve It Once With rerere

AI 導讀

文章示範使用 Git rerere 處理兩個編碼智能體修改同一檔案造成的重複衝突:手動解決並提交一次後,後續 rebase 遇到相同衝突時,Git 會重用該修正。重播後檔案仍處於衝突狀態,需檢查、暫存並繼續 rebase。rerere 只匹配相同衝突,作者亦建議每次重播後執行測試。

正文

You gave Claude Code the login bug and Codex the auth cleanup. Each one worked on its own branch, and each one finished. Before landing either, you merged both into a scratch branch to run the tests together. The guard clause in src/login.js conflicted. You read both versions, wrote the line that keeps both fixes, and the tests passed.

Then you landed the first agent for real and rebased the second onto main. Same file, same line, same conflict, and you resolve it again by hand. If an agent touches that branch once more before it lands, you may end up doing it a third time.

Git has a feature for exactly this. git rerere stands for "reuse recorded resolution" (Pro Git). It records how you resolved a conflict, and the next time the identical conflict shows up, it writes your resolution into the file for you.

Build the two-agent conflict

Everything below is plain git and a shell. It uses git init -b and git worktree, so any recent Git works. Start with a repo and one file:

mkdir rerere-demo && cd rerere-demo
git init -q -b main
mkdir src
cat > src/login.js <<'EOF'
export function login(user, pass) {
  if (!user) return { ok: false };
  return { ok: pass.length > 0 };
}
EOF
git add src && git commit -qm "Initial commit"

Give each agent its own branch and worktree, the way you would before starting two agents in parallel:

git worktree add -q -b hermes/fix-login ../fix-login
git worktree add -q -b hermes/tidy-auth ../tidy-auth

Now play the agents. The first one rejects an empty password:

cd ../fix-login
cat > src/login.js <<'EOF'
export function login(user, pass) {
  if (!user || !pass) return { ok: false };
  return { ok: true };
}
EOF
git commit -qam "Reject empty password"

The second one trims the username, on the same line:

cd ../tidy-auth
cat > src/login.js <<'EOF'
export function login(user, pass) {
  if (!user?.trim()) return { ok: false };
  return { ok: pass.length > 0 };
}
EOF
git commit -qam "Trim the username"

Both changes are reasonable on their own. Together they edit the same lines from the same starting point, which is a textbook content conflict.

Turn on rerere and resolve once

Go back to the main checkout and switch rerere on:

cd ../rerere-demo
git config rerere.enabled true

That is the whole setup. The git-rerere docs say git merge runs rerere automatically when an automerge fails, and git commit runs it again when you commit the merge result. You don't call it yourself in the normal flow. (Git also turns it on by default when an rr-cache directory already exists in the repo, per the config reference.)

Now do the trial merge, in a throwaway worktree so main stays untouched:

git worktree add -q -b try-both ../try-both main
cd ../try-both
git merge -q hermes/fix-login
git merge hermes/tidy-auth

The first merge fast-forwards. The second stops with CONFLICT (content): Merge conflict in src/login.js, and with rerere on, Git adds a line that starts with Recorded preimage for and names the file. The preimage is the conflicted file, markers and all. Resolve it so both fixes survive:

export function login(user, pass) {
  if (!user?.trim() || !pass) return { ok: false };
  return { ok: true };
}

Before you commit, two subcommands let you look at what rerere is about to remember:

git rerere status   # paths whose resolution will be recorded
git rerere diff     # conflicted version vs your current edit
git add src/login.js
git commit --no-edit

git rerere status prints the paths whose resolution rerere will record. git rerere diff shows the conflicted version against what is in the file now, which is the resolution it is about to store. When you commit, Git prints a line starting with Recorded resolution for. Run your tests here: this branch is both agents' work combined.

A black rubber stamp pressing the same burnt-orange seal onto two diverging branch lines
Resolve once, and rerere stamps the same fix wherever the identical conflict reappears.

Land the first agent, rebase the second

The trial was for testing. Now land the real thing. Merge the first agent into main, then rebase the second agent's branch onto it, from that agent's own worktree:

cd ../rerere-demo
git merge -q hermes/fix-login
cd ../tidy-auth
git rebase main

The rebase hits the same conflict, because it compares the same two versions of those lines. This time Git prints Resolved 'src/login.js' using previous resolution. and the file already holds the combined line. The Pro Git chapter shows the same sequence: a merge resolved by hand, then a rebase where rerere fills the resolution in.

Note what rerere did not do. The docs say it "leaves the index file alone", so the path still counts as conflicted and the rebase is still stopped. You look, you stage, you continue:

git diff                  # check what rerere wrote
git add src/login.js
GIT_EDITOR=true git rebase --continue
cd ../rerere-demo
git merge -q hermes/tidy-auth
git worktree remove ../try-both
git branch -D try-both

GIT_EDITOR=true keeps the commit message as it was instead of opening an editor. The last merge is a fast-forward, because the rebased branch already contains the resolution.

There is a setting, rerere.autoUpdate, that stages the replayed file for you. It defaults to false, and I'd leave it there. The rebase docs describe --no-rerere-autoupdate as "a good way to double-check what git-rerere did and catch potential mismerges". With two agents writing code you didn't type, that check is worth the extra git add.

When the recorded fix is wrong

rerere replays whatever you recorded, including mistakes. Say that in the trial merge you had kept the trimmed username and the return { ok: true } line, and dropped the password check:

export function login(user, pass) {
  if (!user?.trim()) return { ok: false };
  return { ok: true };
}

That logs in anyone with an empty password, and rerere will write it back every time the conflict returns. When it does, while the rebase is stopped on the conflict, throw the recording away and rebuild the markers:

git rerere forget src/login.js
git checkout --conflict=merge src/login.js
# fix the file by hand, then:
git rerere
git add src/login.js
GIT_EDITOR=true git rebase --continue

forget resets the resolution rerere recorded for the current conflict in that path (docs). git checkout --conflict=merge recreates the conflicted merge in the file, markers included (docs). Running git rerere after your fix records the new resolution, the same thing git commit does in the merge flow.

A few limits worth knowing before you rely on it:

  • It only matches identical conflict hunks. If an agent rewrites its version of the line, the conflict is new and you resolve it once more.
  • It compares text only. A replayed resolution can be wrong for code that changed elsewhere, so run your tests after every replay.
  • git rerere gc prunes old records: unresolved ones after 15 days and resolved ones after 60, set by gc.rerereUnresolved and gc.rerereResolved (docs). Raise those if your agent branches live longer.
  • It finds conflicts by their markers. A file that already contains lines that look like <<<<<<< can confuse it, and the docs point to the conflict-marker-size attribute as the fix.

Running the same flow in Hermes IDE

I build Hermes IDE, so weigh this part with that in mind. It is a desktop app that runs Claude Code, Codex and other agents side by side, each in its own real terminal. The setup above is how it already works: ⌘N (Ctrl+Shift+N on Windows and Linux) starts each task on its own hermes/<name> branch in its own worktree, with the task as the agent's first prompt (README).

Two agent sessions side by side in Hermes IDE, each in its own hermes/ worktree, with the sessions sidebar on the left
Codex on orders-types and Claude Code on orders-api, each in its own hermes/ worktree, with status, context use, cost and branch in the sessions sidebar.

That layout is why rerere pays off here. All worktrees of a repo share one common .git directory, and rr-cache, where rerere keeps its records, is marked as shared in Git's own list of common directories. The repository config is shared too (repository layout). So git config rerere.enabled true once is enough, and a conflict you resolve in one task's worktree replays in any other. In the demo, you recorded it in ../try-both and Git replayed it in ../tidy-auth.

The Review Desk's Worktrees tab in Hermes IDE listing one worktree per task with its hermes/ branch, age and disk size
The Review Desk's Worktrees tab: one Hermes worktree each for orders-api, web-app and orders-types, with each worktree's hermes/ branch, task, age and disk size.

The order in Hermes is the same as in the shell. Land commits, opens a pull request or merges locally into the branch the task started from, and it can be undone. Land the first task with a local merge (or pull the base branch once its pull request merges), so the base branch holds the first agent's change. Rebase the second task's branch onto that branch from a terminal in its worktree, and rerere fills in the line you already resolved. Review the file, git add it, run git rebase --continue, then Land the second task. Its branch now contains the resolution, so the merge has nothing left to fight over, as long as nothing else landed in between.

If you'd rather ask the agent to run the rebase, the same applies. rerere's records and config live in the repository, so the agent's git rebase main picks up the recorded resolution exactly as yours would. Hermes never types into an agent's terminal on its own, so you are the one who asks.


If this was useful

Two agents on two branches is the simplest multi-agent setup there is, and it already produces repeat conflicts. The AI Agents Pocket Guide covers the patterns around that: splitting work between agents, keeping them out of each other's way, and putting a human check where it counts. Hermes IDE is free to download, source-available under BSL 1.1, on macOS, Windows and Linux, and the code is on GitHub.

AI Agents Pocket Guide: Patterns for Building Autonomous Systems with LLMs

來源:DEV Community · dev.to