一. 前言:同一個衝突,為什麼要解第二次? #
你在功能分支上埋頭開發兩週,每天都把最新的 main rebase 進來。
偏偏團隊也在改同一份設定檔,於是那三行衝突像固定打卡一樣,每隔幾天就回來一次。
第一次仔細判斷、跑測試、完成解法很合理;第五次還靠記憶重打一遍,就只是把腦力浪費在重播。
Git 其實內建了一個低調工具:rerere,名稱來自 reuse recorded resolution。
它會記住「某種衝突長什麼樣」以及「你最後怎麼解」,下次遇到相同或可套用的衝突時,把舊解法放回工作樹。
這篇拍拍君會帶你完成一條可重做、可檢查的流程:
- 在單一 repo 或全域啟用 rerere;
- 製造並手動解掉第一次衝突;
- 重跑 merge,觀察舊解法自動出現;
- 用
status、remaining、diff檢查紀錄; - 撤銷錯誤記憶,管理保留期限;
- 把 rerere 放進 rebase 與團隊工作流程,又不跳過 review。 如果你還不熟悉 rebase 本身,先看 Git rebase 完全攻略;如果是不小心把 commit 弄丟,該找的是 Git reflog 救援實戰。
二. rerere 到底記住了什麼? #
一般 conflict 有兩個重要狀態:
- Git 自動合併失敗後,留下帶 conflict markers 的內容;
- 開發者判斷語意後,寫出最後接受的內容。
rerere 會把前者視為
preimage,把解完衝突的版本視為postimage。 紀錄預設放在目前 repository 的:
.git/rr-cache/
它不是 commit,也不會自動 push 到遠端。
當相同 conflict hunk 再次出現時,Git 會正規化衝突內容、查找既有紀錄,再嘗試把 postimage 套回工作樹。
這裡有三個必須先記住的界線:
- 它重用的是文字衝突的解法,不是替你判斷新的商業邏輯;
- 套用成功不等於測試成功,仍要 review diff;
- 預設不會因為內容看起來已解好,就偷偷替你完成 commit。 把 rerere 想成「有記憶的衝突助手」,而不是「自動批准 merge 的機器人」,就不容易用歪。
三. 啟用方式:先從單一 Repo 開始 #
rerere 是 Git 內建功能,不需要額外安裝。 先確認版本:
git --version
只在目前 repository 啟用:
git config rerere.enabled true
檢查設定來源:
git config --show-origin --get rerere.enabled
確定習慣這個流程後,也可以全域開啟:
git config --global rerere.enabled true
想確認最後生效的值,可以加上 --show-scope:
git config --show-origin --show-scope --get-all rerere.enabled
拍拍君建議第一次先用 repo-local 設定。 若要關閉目前 repository 的 rerere:
git config rerere.enabled false
四. 建立實驗 Repo:親手製造一次衝突 #
先建立一個不會傷到正式工作的練習 repository:
mkdir rerere-lab
cd rerere-lab
git init -b main
git config user.name "拍拍君"
git config user.email "pypy@example.invalid"
git config rerere.enabled true
建立第一版重試設定:
cat > retry.conf <<'EOF'
mode = linear
max_retries = 2
delay_seconds = 1
EOF
git add retry.conf
git commit -m "add retry defaults"
功能分支把策略改成 exponential backoff:
git switch -c feature/backoff
cat > retry.conf <<'EOF'
mode = exponential
max_retries = 5
delay_seconds = 1
EOF
git add retry.conf
git commit -m "use exponential backoff"
回到 main,模擬另一位開發者調整相同區域:
git switch main
cat > retry.conf <<'EOF'
mode = linear
max_retries = 4
delay_seconds = 2
EOF
git add retry.conf
git commit -m "tune retry limits"
現在合併功能分支:
git merge feature/backoff
Git 會留下 conflict markers,並出現類似訊息:
CONFLICT (content): Merge conflict in retry.conf
Recorded preimage for 'retry.conf'
Automatic merge failed; fix conflicts and then commit the result.
Recorded preimage 就是 rerere 開始工作的證據。
五. 第一次手動解:把正確答案教給 rerere #
先看哪些檔案正在被追蹤:
git rerere status
輸出會包含:
retry.conf
假設團隊判斷後,決定採用 exponential mode、保留較高重試次數,也採用 main 的兩秒基準:
cat > retry.conf <<'EOF'
mode = exponential
max_retries = 5
delay_seconds = 2
EOF
在 git add 前,可以看 rerere 眼中的解題進度:
git rerere diff
也別忘了基本檢查:
git diff --check
git diff
確認內容後,標記為 resolved 並完成 merge:
git add retry.conf
git commit -m "merge backoff with tuned retry limits"
這時 Git 會記錄 resolved image,通常會看到:
Recorded resolution for 'retry.conf'.
你可以檢查 cache 是否建立:
find .git/rr-cache -maxdepth 2 -type f -print
常見檔名包括 preimage 與 postimage;目錄名稱則是 Git 用來查找衝突紀錄的識別值。
六. 第二次衝突:讓舊解法自動回來 #
現在故意回到 merge 前,再執行完全相同的 merge:
git reset --hard HEAD^
git merge feature/backoff
你仍可能看到 merge 回報 conflict,但多一行關鍵訊息:
Resolved 'retry.conf' using previous resolution.
查看檔案:
cat retry.conf
內容已經回到先前接受的版本:
mode = exponential
max_retries = 5
delay_seconds = 2
不過,預設情況下 index 仍保留 unmerged 狀態。
git status --short
git diff
git diff --check
如果專案有測試或設定驗證器,現在就跑:
./scripts/validate-config retry.conf
pytest -q
確認解法在這一次仍然正確後,才完成 staging:
git add retry.conf
git commit -m "merge feature/backoff"
rerere 幫你省掉重打答案,沒有省略 review、測試與 commit。
七. status、remaining、diff:三個檢查視角
#
遇到多檔衝突時,不要只盯著 git status。
7.1 git rerere status
#
列出 rerere 正在記錄解法的衝突路徑:
git rerere status
它回答的是:「哪些 conflict resolution 會成為之後的記憶?」
7.2 git rerere remaining
#
列出還沒有被 rerere 自動解決的路徑:
git rerere remaining
如果有 submodule conflict 或 rerere 無法追蹤的項目,也可能出現在這裡。 它回答的是:「我還需要親手處理哪些地方?」
7.3 git rerere diff
#
顯示目前手動解題內容相對於 conflict preimage 的差異:
git rerere diff
它回答的是:「我把衝突改成了什麼?」 實務上可以依序使用:
git status --short
git rerere status
git rerere remaining
git rerere diff
git diff --check
第一行看整個 index / working tree,後三行聚焦 rerere,最後一行抓 whitespace error。
八. Rebase 實戰:真正容易反覆遇到的場景 #
rerere 最有價值的地方,通常不是把同一個 merge 故意跑兩次,而是長壽命 feature branch。 假設你定期執行:
git fetch origin
git switch feature/payments
git rebase origin/main
第一次遇到衝突時,正常解題:
git rerere status
# 編輯衝突檔、跑測試
git add src/payment.py
git rebase --continue
過幾天 main 又向前移動,再 rebase 時,只要 conflict shape 仍可辨識,rerere 就會嘗試重用先前解法。
你仍要做三件事:
git diff
pytest -q
git add src/payment.py
然後才繼續:
git rebase --continue
如果整次 rebase 方向錯了:
git rebase --abort
Git 會清理這次操作相關的 rerere working metadata;已完成並保留的舊 resolution records 不等於全部被刪掉。 想回顧 rebase 的正確基礎與 force push 邊界,請搭配 Git rebase 完全攻略。
九. rerere.autoupdate:方便,但先理解代價
#
預設 rerere 套用舊解法後,只更新 working tree,不會直接把檔案標成 resolved。 你可以開啟自動更新 index:
git config rerere.autoupdate true
之後,若舊解法乾淨套用,Git 會把結果放進 index。
它能少打一個 git add,但也縮短了你察覺錯誤解法的距離。
拍拍君的建議很直接:
- 個人小專案、測試完整:可以評估開啟;
- 高風險設定、資料 migration、安全規則:保留預設
false; - 不論是否 autoupdate,都要看 staged diff。 查看 staged 結果:
git diff --cached
只在單次命令改變行為,也可以使用:
git -c rerere.autoupdate=true merge feature/backoff
想回到保守模式:
git config rerere.autoupdate false
十. 記錯答案怎麼辦?forget、clear 與 gc
#
rerere 的記憶可以管理,不必因為一次解錯就整個關掉。
10.1 忘掉目前路徑的舊解法 #
當同一個 conflict 正在發生,而舊 resolution 已經不適用:
git rerere forget retry.conf
接著重新編輯、測試、git add,讓 rerere 記住新的正確解法。
forget 接受 pathspec,因此能精準處理特定檔案:
git rerere forget 'src/payment/*.py'
10.2 清掉目前未完成操作的 rerere metadata #
如果要放棄本次 conflict resolution:
git rerere clear
通常 merge/rebase 的 --abort 或 --skip 也會處理對應狀態。
先優先使用所屬操作的正式命令,例如:
git merge --abort
# 或
git rebase --abort
10.3 清理過期紀錄 #
執行 garbage collection:
git rerere gc
Git 預設在這個命令執行時,清除超過 15 天的 unresolved records,以及超過 60 天的 resolved records。 可調整保留時間:
git config gc.rerereUnresolved 10
git config gc.rerereResolved 120
這些數值代表天數。
不要把 rm -rf .git/rr-cache 當成日常清理流程;先用 Git 提供的命令,範圍比較清楚。
十一. 常見陷阱:它能重播文字,不能驗證語意 #
11.1 同樣的文字,不一定有同樣的需求 #
舊解法可能乾淨套用,但 API contract、資料格式或安全政策已經改變。 因此 rerere 命中後,最少仍要跑:
git diff
git diff --check
# 專案自己的 test / lint / type check
11.2 Binary 與 submodule 不是它的強項 #
rerere 依賴檔案中的 conflict markers 來辨識文字衝突。 圖片、產出檔、某些 rename/modify 情境,以及 submodule conflict,不應期待它像一般文字 hunk 一樣處理。
11.3 自訂 conflict marker 要小心 #
如果檔案本身就含有類似 <<<<<<< 的內容,rerere 可能難以正確記錄。
Git 官方文件建議可用 .gitattributes 的 conflict-marker-size 調整特定路徑的 marker 長度:
fixtures/conflicts/*.txt conflict-marker-size=32
這是特殊情況的修正,不需要對全專案亂加。
11.4 Cache 是本機的,不是團隊真理 #
.git/rr-cache/ 不在一般 commit history 裡。
技術上可以搬移 cache,但把個人 resolution database 當共享 artifact,會帶來來源、版本與信任問題。
團隊真正該共享的是:
- 測試與驗證規則;
- merge / rebase policy;
- 對重要衝突的設計決策;
- code review,而不是黑盒 cache。
十二. 一套安全的團隊工作流程 #
拍拍君推薦把 rerere 放在下列流程裡:
1. 啟用 rerere
2. 發生 conflict
3. 用 status / remaining 盤點
4. 手動解題或讓舊解法套用
5. 檢查 working tree / staged diff
6. 跑 test、lint、type check
7. git add
8. merge commit 或 rebase --continue
9. 若舊答案過期,使用 rerere forget
對應命令可以整理成:
git status --short
git rerere status
git rerere remaining
git rerere diff
git diff --check
pytest -q
ruff check .
git add <resolved-paths>
git rebase --continue # 或 git commit
如果 repository 特別關鍵,建議先不要啟用 rerere.autoupdate。
等團隊熟悉「舊答案只是候選答案」後,再評估是否自動 staging。
你也可以把檢查清單寫進 CONTRIBUTING.md,讓 rerere 不只是一位開發者知道的祕技。
結語 #
git rerere 做的事情很單純:記住你已經驗證過的衝突解法,在相同問題回來時先幫你填上答案。
真正的價值不是「自動解 conflict」這句口號,而是把重複勞動變成可檢查的本機記憶。
安全使用時,請牢記三件事:
- 第一次 resolution 要仔細,因為它可能被重用;
- 每次重用後仍要 review diff、跑測試;
- 舊答案失效時,用
forget明確重教,不要硬吞。 先在練習 repo 開啟rerere.enabled,親手解一次、回退、再合併一次。 看到Resolved using previous resolution的瞬間,你就會知道:有些衝突,真的不用再解第二遍。🔁