章節 ▾ 第二版

7.9 Git 工具 - Rerere

Rerere

git rerere 功能是一個有點隱蔽的特性。這個名字代表“重用已記錄的解決方案”(reuse recorded resolution),顧名思義,它允許你讓 Git 記住你如何解決一個衝突塊(hunk conflict),以便下次遇到相同的衝突時,Git 可以自動為你解決它。

在許多場景中,此功能會非常方便。文件中提到的一個例子是,當你想要確保一個長期存在的話題分支最終能幹淨地合併,但又不希望有大量中間合併提交來弄亂你的提交歷史。啟用 rerere 後,你可以偶爾嘗試合併,解決衝突,然後撤銷合併。如果你持續這樣做,那麼最終的合併就會很容易,因為 rerere 可以自動為你完成所有操作。

如果你想保持一個分支的 rebase 狀態,這樣你就不必每次 rebase 時都處理相同的衝突,那麼也可以使用同樣的策略。或者,如果你已經合併了一個分支並解決了一堆衝突,然後決定改為對其進行 rebase,你很可能不必再處理所有相同的衝突。

rerere 的另一個應用場景是,你偶爾會將一堆正在發展的話題分支合併到一個可測試的 HEAD 上,就像 Git 專案本身經常做的那樣。如果測試失敗,你可以回滾合併並重新進行,移除導致測試失敗的話題分支,而無需再次解決衝突。

要啟用 rerere 功能,你只需執行此配置設定

$ git config --global rerere.enabled true

你也可以透過在特定倉庫中建立 .git/rr-cache 目錄來開啟它,但配置設定更清晰,並且能為你全域性啟用該功能。

現在我們來看一個簡單的例子,類似於我們之前的例子。假設我們有一個名為 hello.rb 的檔案,內容如下

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

在一個分支中,我們將單詞“hello”改為“hola”,然後,在另一個分支中,我們將“world”改為“mundo”,就像之前一樣。

Two branches changing the same part of the same file differently
圖 160. 兩個分支以不同方式修改同一檔案的同一部分

當我們合併這兩個分支時,我們會遇到一個合併衝突

$ git merge i18n-world
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Recorded preimage for 'hello.rb'
Automatic merge failed; fix conflicts and then commit the result.

你應該會注意到其中新增的一行 Recorded preimage for FILE。除此之外,它應該看起來與普通的合併衝突完全一樣。此時,rerere 可以告訴我們一些事情。通常,你可能會在此處執行 git status 來檢視所有衝突的內容

$ git status
# On branch master
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add <file>..." to mark resolution)
#
#	both modified:      hello.rb
#

然而,git rerere 也會透過 git rerere status 告訴你它已經記錄了合併前的哪些狀態

$ git rerere status
hello.rb

git rerere diff 將顯示當前解決方案的狀態——你從什麼開始解決,以及你將其解決成了什麼。

$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,11 @@
 #! /usr/bin/env ruby

 def hello
-<<<<<<<
-  puts 'hello mundo'
-=======
+<<<<<<< HEAD
   puts 'hola world'
->>>>>>>
+=======
+  puts 'hello mundo'
+>>>>>>> i18n-world
 end

此外(這與 rerere 並沒有直接關係),你可以使用 git ls-files -u 來檢視衝突檔案以及它們之前、左邊和右邊的版本

$ git ls-files -u
100644 39804c942a9c1f2c03dc7c5ebcd7f3e3a6b97519 1	hello.rb
100644 a440db6e8d1fd76ad438a49025a9ad9ce746f581 2	hello.rb
100644 54336ba847c3758ab604876419607e9443848474 3	hello.rb

現在你可以將其解決為 puts 'hola mundo',然後再次執行 git rerere diff 來檢視 rerere 會記住什麼

$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,7 @@
 #! /usr/bin/env ruby

 def hello
-<<<<<<<
-  puts 'hello mundo'
-=======
-  puts 'hola world'
->>>>>>>
+  puts 'hola mundo'
 end

所以這基本上表示,當 Git 在 hello.rb 檔案中看到一個衝突塊,其中一邊是“hello mundo”,另一邊是“hola world”時,它會將其解決為“hola mundo”。

現在我們可以將其標記為已解決並提交

$ git add hello.rb
$ git commit
Recorded resolution for 'hello.rb'.
[master 68e16e5] Merge branch 'i18n'

你可以看到它“為 FILE 記錄瞭解決方案”。

Recorded resolution for FILE
圖 161. 為 FILE 記錄的解決方案

現在,讓我們撤銷該合併,然後將其 rebase 到我們的 master 分支之上。我們可以使用 git reset 將分支回溯,正如我們在揭秘 Git Reset 中看到的那樣。

$ git reset --hard HEAD^
HEAD is now at ad63f15 i18n the hello

我們的合併已撤銷。現在讓我們 rebase 這個話題分支。

$ git checkout i18n-world
Switched to branch 'i18n-world'

$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: i18n one word
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Resolved 'hello.rb' using previous resolution.
Failed to merge in the changes.
Patch failed at 0001 i18n one word

現在,我們得到了預期的相同合併衝突,但請注意 Resolved FILE using previous resolution 這一行。如果我們檢視檔案,會發現它已經解決了,裡面沒有合併衝突標記。

#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

此外,git diff 會向你展示它是如何自動重新解決的

$ git diff
diff --cc hello.rb
index a440db6,54336ba..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
  #! /usr/bin/env ruby

  def hello
-   puts 'hola world'
 -  puts 'hello mundo'
++  puts 'hola mundo'
  end
Automatically resolved merge conflict using previous resolution
圖 162. 使用先前解決方案自動解決的合併衝突

你也可以使用 git checkout 重新建立衝突檔案狀態

$ git checkout --conflict=merge hello.rb
$ cat hello.rb
#! /usr/bin/env ruby

def hello
<<<<<<< ours
  puts 'hola world'
=======
  puts 'hello mundo'
>>>>>>> theirs
end

我們在高階合併中看到了一個例子。但現在,讓我們透過再次執行 git rerere 來重新解決它

$ git rerere
Resolved 'hello.rb' using previous resolution.
$ cat hello.rb
#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

我們已經使用 rerere 快取的解決方案自動重新解決了檔案。你現在可以新增並繼續 rebase 來完成它。

$ git add hello.rb
$ git rebase --continue
Applying: i18n one word

因此,如果你經常進行重複合併,或者想要讓話題分支與 master 分支保持最新而無需大量合併,或者你經常 rebase,那麼可以啟用 rerere 來讓你的工作更輕鬆一些。

scroll-to-top