快轉到主要內容
  1. 教學文章/

Git rerere 實戰:記住衝突解法,讓 Rebase 與 Merge 不再重做

·8 分鐘· loading · loading · ·
Git Rerere Merge-Conflict Rebase Merge Version-Control Developer-Tools
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
版本控制: Git - 本文屬於一個選集。
§ 13: 本文

featured

一. 前言:同一個衝突,為什麼要解第二次?
#

你在功能分支上埋頭開發兩週,每天都把最新的 main rebase 進來。 偏偏團隊也在改同一份設定檔,於是那三行衝突像固定打卡一樣,每隔幾天就回來一次。 第一次仔細判斷、跑測試、完成解法很合理;第五次還靠記憶重打一遍,就只是把腦力浪費在重播。 Git 其實內建了一個低調工具:rerere,名稱來自 reuse recorded resolution。 它會記住「某種衝突長什麼樣」以及「你最後怎麼解」,下次遇到相同或可套用的衝突時,把舊解法放回工作樹。 這篇拍拍君會帶你完成一條可重做、可檢查的流程:

  • 在單一 repo 或全域啟用 rerere;
  • 製造並手動解掉第一次衝突;
  • 重跑 merge,觀察舊解法自動出現;
  • statusremainingdiff 檢查紀錄;
  • 撤銷錯誤記憶,管理保留期限;
  • 把 rerere 放進 rebase 與團隊工作流程,又不跳過 review。 如果你還不熟悉 rebase 本身,先看 Git rebase 完全攻略;如果是不小心把 commit 弄丟,該找的是 Git reflog 救援實戰

二. rerere 到底記住了什麼?
#

一般 conflict 有兩個重要狀態:

  1. Git 自動合併失敗後,留下帶 conflict markers 的內容;
  2. 開發者判斷語意後,寫出最後接受的內容。 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

常見檔名包括 preimagepostimage;目錄名稱則是 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。

七. statusremainingdiff:三個檢查視角
#

遇到多檔衝突時,不要只盯著 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

十. 記錯答案怎麼辦?forgetcleargc
#

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 官方文件建議可用 .gitattributesconflict-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」這句口號,而是把重複勞動變成可檢查的本機記憶。 安全使用時,請牢記三件事:

  1. 第一次 resolution 要仔細,因為它可能被重用;
  2. 每次重用後仍要 review diff、跑測試;
  3. 舊答案失效時,用 forget 明確重教,不要硬吞。 先在練習 repo 開啟 rerere.enabled,親手解一次、回退、再合併一次。 看到 Resolved using previous resolution 的瞬間,你就會知道:有些衝突,真的不用再解第二遍。🔁

延伸閱讀
#

版本控制: Git - 本文屬於一個選集。
§ 13: 本文

相關文章

Git reflog 救援實戰:找回 reset、rebase 後消失的 Commit
·9 分鐘· loading · loading
Git Reflog Recovery Reset Rebase Commit Version-Control
Git rebase 完全攻略:整理 commit 歷史、互動式 rebase 與衝突處理
·11 分鐘· loading · loading
Git Rebase Interactive-Rebase Commit-History Version-Control
Git bisect 實戰:快速定位壞 commit 與除錯流程完全攻略
·9 分鐘· loading · loading
Git Bisect Debugging Regression Version-Control
Git worktree 實戰:同時開多個分支、平行測試與安全清理完全攻略
·11 分鐘· loading · loading
Git Worktree Branch Workflow Version-Control
Git cherry-pick 實戰:精準搬運 commit、修補 hotfix 與分支同步
·11 分鐘· loading · loading
Git Cherry-Pick Hotfix Commit Version-Control
Git stash 實戰:暫存工作現場、切換任務與 patch 管理完全攻略
·11 分鐘· loading · loading
Git Git-Stash Patch Workflow Version-Control