4

Git Cherry Pick 的後遺症

 1 year ago
source link: https://blog.darkthread.net/blog/git-cherry-pick-cons/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Git Cherry Pick 的後遺症

2020-10-15 10:26 PM 9 10,594

Git 版控入門算算一年多,自覺還算上手,能善用 Branch、Commit、Reset,要把程式改爛到回不去還真有些難度。而最美妙的是 - Git 屬分散式版控,在自己的機器可以隨意 Commit、Reset、開 Branch ,天王老子都不能管你,只要送上 TFS 或 Github 前先用 Squash、Rebase 好好整理,Push 到伺服器的程式碼整整齊齊,沒人會知道這段程式曾改過十幾次理由還超低級。寫程式時像有張可靠的安全網時時保護你,能放膽嘗試各種構想挑戰超越自己,Coding 就是要這樣才盡興!

推坑時間:如果你有在寫程式卻看不懂上面這一大堆術語?那你一定也不知道自己錯過什麼好東西,大力推薦這個要花點時間學但絕對值回票價的神兵利器 - Git!!

【延伸閱讀】

不過,學習很難一次到位,入門時遺漏的訣竅技巧,要靠實戰經驗補齊。最近對 Git 的 Cherry Pick 有些心得,整理分享給大家參考。

我們都知道,將 Commit 搬到其另一條 Branch,除了 MergeRebase外,還有一個好用功能叫 Cherry-Pick,可以從其他 Branch 單獨挑幾個 Commit 搬到現在的 Branch 上。我有一些小型專案只區分 master 跟 release 兩個 Branch,遇到想先上線部分修改時,用 Cherry-Pick 挑選要上線 Commit 從 master 搬到 release 是簡單又直覺的做法。

不過,使用 Cherry Pick 是有後遺症的,之前看的中文文章很少提到這部分,就由我來補上吧!

使用 Cherry Pick 主要衍生兩個問題,一是搬過去的 Commit 雖然說明訊息與來源相同(如果選擇 Cherry Pick 後手動 Commit,則連說明也不會相同)但二者 Hash 值可能不同,無法建立連結,未來不易追溯完整修改歷程。

第二個問題則是:Cherry Pick 過再做 Merge,會產生重複 Commit 項目。

為了反覆實驗,我寫了一串 DOS 指令重現問題:

rem 清空上次 Git 測試結果
rmdir .git /s /q
del . /q
rem 建立 Git Repository
git init
rem 建立 file1.txt 並 Commit
echo "1234" > file1.txt
git add . && git commit -m "init"
rem 建立 relase Branch
git branch release
rem 在 master 加入 file2.txt 並 Commit
git checkout master
echo "ABCD" > file2.txt
git add .
git commit -m "commit for later merge"
rem 繼續加入 file3.txt 並 Commit
echo "Git Rocks!" > file3.txt
git add .
git commit -m "commit for cherry-pick"
rem 取得 file3.txt 這個 Commit 的 Hash 值稍後 Cherry Pick 要用
for /f %i in ('git rev-parse HEAD') do set hash=%i
rem 切到 release 並 Cherry Pick file3.txt 那個 Commit
git checkout release
git cherry-pick %hash%
rem 回到 master 繼續新增 file4.txt 並 Commit
git checkout master
echo "Hello, World" > file4.txt
git add .
git commit -m "commit for merge"
rem 將 master 合併到 release
git checkout release
git merge master

註:利用 FOR /F 取 Commit Hash 的原理可參見 如何利用批次檔(Batch)讀取指令執行的結果或文字檔案內容 by 保哥

在這個展示中,我開了 master 跟 relese 兩個 Branch,在 master 加了三個 Commit,先 Cherry Pick 第二個 Commit 到 release,之後再將 master Merge 到 realse。

master 歷史如下:

Fig1_637383688655595643.png

release 有幾個重點,1 所指的 Commit 是用 Cherry Pick 拉過來的,雖然訊息相同,但 Hash ID 586de312 與來源 d0fb85f3 不同。之後 Merge 把 master 的所有更新合進來時,剛才 Cherry Pick 拉進來的 Commit 又再出現一次 (2 所指的地方)。

Fig2_637383688656174930.png

產生重複 Commit 是預期中的行為,如果要排除的做法一般是 master 先對 release 做 Rebase 再 Merge:

Fig3_637383688656727683.png

這樣子,master 會調整 Commit 順序向 release 看齊,"commit for cherry-pick" 變成第一個,Hash Id 也從 d0fb85f3 變成 586de312,其餘兩個 Commit 也要重算過,Hash Id 都跟原本不同:

Fig4_637383688657209026.png

master 都配合到這地步,Merge 到 release 當然順暢無比,連小耳朵都沒有:

Fig5_637383688657625641.png

不過,如果 master 曾經 Push 到伺服器端,Rebase 會修改歷史帶來困擾,顯然不是個好選項。

查了幾篇英文文章,都提到 Cherry Pick 的負面影響,並強調 Git 社群不推 Cherry Pick:

Cherry picking is commonly discouraged in developer community. The main reason is because it creates a duplicate commit with the same changes and you lose the ability to track the history of the original commit. If you can merge, then you should use that instead of cherry picking. Use it with caution!
在開發社群通常不鼓勵使用Cherry Pick,主要原因是它會產生重複 Commit 且無法追蹤原始修改歷程,如果能 Merge 就別用它,請小心使用。

Even though this feature is interesting and awesome, it has been discouraged in the git community. The main reason is that it creates a duplicate commit with the same changes and you lose the ability to track the history of the original commit.
Also note that if you are cherry-picking a lot of commits out of order, those will be reflected on your branch in the order you cherry picked, not the on the chronological order of the original commits. Sometimes this may lead to undesirable results in your branch
雖然 Cherry Pick 很威,但在 Git 社群並不鼓勵使用,主要原因是它會產生重複的 Commit 且無法追蹤原始修改歷程。Cherry Pick 加入的 Commit 在 Branch 上會以 Cherry Pick 操作時間而非修改發生的時間排序,有時可能導致悲劇。

至此得到結論:

  1. Cherry Pick 之後再 Merge 會產生重複的 Commit
  2. 透過 Cherry Pick 建立的 Commit 將難以追溯完整修改歷程
  3. 如果可以 Merge,就別用 Cherry Pick

好,回到實務上,該如何防止 Cherry Pick 造成的副作用?依本文的 master/release 案例,我想到幾種可行策略:

  1. 採行「release 與 master 脫鉤」政策,讓 release 的 Commit 單純反映每次部署或發行異動,之後 master 到 release 一律用 Cherry Pick,追蹤程式修改歷程由 master 及其他 branch。
  2. 將 master 升級成確認要部署發行才能合併(性質拉高到等同 release),開發人員一律另開 Branch 開發,並三不五時對 master 進行 Rebase,如此即使用 Cherry Pick 拉 Commit 到 master,透過 Rebase 重新對齊,之後開發告一段落要 Merge 回 master 時歷史軌跡完整且不會有 Commit 重複問題。代價是分支變多,管理與合併成本會上升,而各開發人員的本機 Branch,還是要設法 Push 到伺服器端保存,因為常 Rebase 得開放 push -f,或許該跟 master/release 分開管理。
  3. 一言不合就開 Branch。大小修改異動都另起一個 Branch,改好再 Merge 回去,這樣子應該就用不到 Cherry-Pick 了。(雖然開 Branch 很便宜,但數量一多要合併會讓人頭暈吧?)

抛磚引玉丟出這個議題,歡迎大家分享私房祕技。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK