2013-08-07

打 patch 時遇到 conflict 怎麼辦

工作中常常會需要把別人提供的 patch 檔加入自己的工作目錄,在 git 中有兩個相關的指令 git applygit am。如果 patch 成功,那就皆大歡喜,可是最麻煩的是他常常會失敗,這篇文章簡單紀錄一下我是怎麼處理這個 patch 的流程。

這個問題跟 merge 產生的 merge conflict 是不同的,merge 的過程,你會有 local、remote、base 三個版本的檔案,這三個都是完整的檔案,所以可以很簡單的透過 merge tool 的 three-way merge 解決。

而 patch 不同,你有的只有一份完整的檔案,和只記了修訂差異的 patch 檔。相當於你不知道 base 是什麼,也沒有完整的 remote,只有 remote - base 的差,所以整個資訊量是差很多的。

首先,可以用這個指令打 patch
git apply sample.patch

如果不幸失敗了,原始檔案會維持不動,等於你什麼都沒有做。可以換成
git apply --reject sample.patch

這樣 git 會把 patch 檔中可以合進去的 hunk 加進去,剩下的放在 *.rej 中,* 是對應的檔名。所以到目前為止,有稍微往前一點了,再來就是看 *.rej 裡的 change 要怎麼加。

要知道 patch 打完後,會產生哪些 *.rej,可以透過
git status

就會在 untracked 的部份看到他(如果你沒有把 .rej 加到 .gitignore)
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)

接下來可以使用 wiggle(wiggle - apply rejected patches and perform word-wise diffs),他會更積極的幫你 patch,當然加錯的風險也會變大,記得一定要檢查。

安裝
sudo apt-get install -y wiggle

然後透過這樣的方式使用
wiggle --repalce foo.cpp foo.cpp.rej

他會備份一份原始檔案在 *.porig,然後把可以合進去的 change 直接寫在原始檔中,而無法判定的部份,wiggle 會猜測可能的位置,然後把包含 merge marker 的 change 寫進去,例如
<<<<<<<
Some portion of the original file
|||||||
text to replace
=======
text to replace it with
>>>>>>>

用 diff tool 檢查一下 wiggle 寫進去的東西,例如
vimdiff foo.cpp foo.cpp.porig

然後手動把那些有 conflict marker 的整理好。如果整個都合錯的話,恭喜你,只好把備份檔 *.porig 弄回來,然後用肉眼看 *.rej,通通自己來。

最後,因為 wiggle 會把 conflict marker 寫進去,所以可以在 git commit 時,透過 pre-commit hook 的機制,檢查檔案內是否有包含 conflict marker 作多一層的保險。pre-commit 也不用自己寫,這邊剛好有一個現成的 project