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

Git cherry-pick 實戰:精準搬運 commit、修補 hotfix 與分支同步

·11 分鐘· loading · loading · ·
Git Cherry-Pick Hotfix Commit Version-Control
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
版本控制: Git - 本文屬於一個選集。
§ 9: 本文

featured

一. 前言:不是所有修改,都值得整條 branch 一起搬走
#

你一定遇過這種場景:功能分支上做了一整串改動,但其中只有一個小修正非常急,你想先把它補到正式環境;或者同事在另一條 branch 修掉一個 bug,你只想拿那一顆 commit,不想把其他還沒 review 的東西一起帶過來。這時候,直接 merge 往往太重,rebase 又是在整理歷史,目的完全不同。

git cherry-pick 的價值,就在於它幫你做一件很精準的事:只把你指定的 commit 套用到目前分支。不是整條 branch 搬家,也不是重寫整段歷史,而是把某顆或某幾顆 commit 的變更內容,重新套到你現在所在的位置。

如果你前面看過拍拍君寫的 Git Branch 策略完全攻略Git rebase 完全攻略Git stash 實戰,那今天這篇剛好把 Git 工作流再補上一塊:branch 告訴你工作線怎麼切,rebase 告訴你歷史怎麼整理,stash 幫你暫存現場,而 cherry-pick 則是負責在多條線之間做「局部搬運」。

這篇文章會從最基本的概念開始,一路講到日常實戰:單顆 commit 搬運、多顆 commit 批次挑選、hotfix 回補、衝突處理、-x-e--no-commit 的用法,以及 cherry-pick merge commit 時要注意什麼。先講結論:這是一把手術刀,不是鏟土機。用得好,它是救火神器;用不好,兩週後你就會看不懂 repo 為什麼同一段修正出現三次。

二. 安裝與基本準備:指令是內建的,觀念才是重點
#

git cherry-pick 是 Git 內建功能,所以不需要另外安裝套件。先確認版本:

git --version

如果你還沒裝 Git,可以用常見方式安裝:

# macOS
brew install git

# Ubuntu / Debian
sudo apt update
sudo apt install git

# Windows
winget install --id Git.Git -e

身份設定如果還沒做,也先補上:

git config --global user.name "拍拍君"
git config --global user.email "pypy@example.com"

真正重要的,其實不是「會不會打 cherry-pick」,而是你有沒有先看懂要搬哪顆 commit。開始之前,至少先熟這兩個指令:

git log --oneline --graph --decorate --all
git show <commit>

第一個用來看歷史圖,第二個用來確認某顆 commit 到底改了什麼。很多 cherry-pick 事故,不是因為這個指令本身很危險,而是因為人根本沒看清楚自己在撿哪一顆。

先假設目前分支長這樣:

A---B---C  main
     \
      D---E---F  feature/payment

其中 D 是付款流程骨架,E 是修正金額四捨五入 bug,F 是第三方 API 串接,還沒測完。這時正式環境很急,你只想把 E 搬回 main。這,就是 cherry-pick 最典型的使用場景。

還有一句話先記起來:你搬的是「變更內容」,不是把原本那顆 commit 原封不動搬過去。Git 會把那顆 commit 的 diff 重新套用到你目前所在的分支,然後產生一顆新的 commit,所以新 hash 幾乎一定不同。內容很像,但不是同一個物件;這也就是為什麼之後看 log、merge 或 blame 時,會跟你直覺中的「複製貼上 commit」不太一樣。

三. 先搞懂核心:cherry-pick 到底做了什麼
#

3.1 最基本的單一 commit 搬運
#

假設你現在在 main

git switch main

然後你確認要搬的 commit 是 e4f5a6b

git cherry-pick e4f5a6b

如果一切順利,Git 會把那顆 commit 的變更套用到 main,並自動建立新的 commit。歷史會從這樣:

A---B---C  main
     \
      D---E---F  feature/payment

變成這樣:

A---B---C---E'  main
     \
      D---E---F  feature/payment

E' 不是原本的 E,而是「把 E 的修改內容重新套到 main 後」產生的新 commit。這個觀念後面非常重要,因為它直接關係到為什麼可能出現重複修正,以及為什麼 cherry-pick 不適合被濫用成長期同步工具。

3.2 它跟 merge 的差別在哪裡?
#

如果這裡你改用:

git merge feature/payment

那你通常會把 DEF 全部一起帶過來。可你現在明明只想要 bug fix,所以 merge 太重。cherry-pick 的重點,就是只取你要的那一部分。它很像從 branch 上抽出一張 patch,但又保留 Git 對內容變更的理解。

3.3 它跟 rebase 又差在哪裡?
#

拍拍君用一句話幫你分:

  • merge:把兩段歷史接起來
  • rebase:把一串 commit 換基底重放
  • cherry-pick:挑個別 commit 套到目前分支

如果你想整理自己 branch 的歷史,通常想到的是 rebase;如果你想把別處某幾顆修正帶來現在這條線,通常想到的才是 cherry-pick。兩者都會「重放變更」,但使用意圖完全不同。

3.4 最容易誤會的點:它不是 branch 同步工具
#

很多人一開始會把 cherry-pick 當成「手動同步 branch」的方式:main 修一顆就 pick 到 release,release 修一顆再 pick 回 main,feature 修一顆又 pick 到 hotfix。短期很爽,長期就很容易把 repo 變成 patch 地獄。同一修正可能出現多次、各分支又補上各自的小改動,最後連自己都看不懂哪些才是真正來源。

所以拍拍君會建議:cherry-pick 很適合局部補丁與精準回補,不適合拿來當整個團隊的主要同步機制。

四. 最常見的實戰:挑單顆、挑多顆、挑完再改
#

4.1 場景一:把 hotfix 從開發分支搬回 main
#

假設你在 feature/payment 上修了一個很急的 bug。先看 log:

git log --oneline

你看到:

f8a1c92 connect provider api
7b3d412 fix rounding bug in total amount
2c9e7ab scaffold payment flow

如果你只想搬中間那顆修正:

git switch main
git cherry-pick 7b3d412

這是最標準用法,特別適合某顆 bug fix 很獨立、其他功能還沒準備好上線、正式環境只想先補一刀的情況。

4.2 場景二:一次挑多顆連續 commit
#

有時你想搬的不只一顆,而且它們剛好是連續的。這時可以直接指定範圍:

git cherry-pick A^..C

這代表把 AC 這段都挑過來。例如:

git cherry-pick 3fd12ab^..91ac44e

這種寫法很適合某個小功能剛好是連續 2 到 3 顆 commit,而你又想保留原本的提交粒度。當然,範圍選錯也很容易把你本來不想要的 commit 一起帶進來,所以在動手前,最好先跑:

git log --oneline --reverse <start>^..<end>

確認清楚再 pick。

4.3 場景三:一次挑多顆不連續 commit
#

如果要搬的是不連續幾顆,可以直接列出來:

git cherry-pick 8a1b2c3 d4e5f6a 7b8c9d0

Git 會依照你列出的順序逐顆套用。這對回補 release branch 很常見:一顆是登入 bug fix,一顆是 API timeout 修正,一顆是 log 補強,它們散落在不同地方,但都想補進 release/1.4

4.4 場景四:先套變更,但先不要立即 commit
#

有時你想把某顆 commit 的內容帶過來,但想順手修改一下,或跟其他變更合成一顆。這時可以用:

git cherry-pick --no-commit <commit>

或簡寫:

git cherry-pick -n <commit>

它會把變更套到工作目錄與 index,但不自動產生 commit。接著你可以檢查狀態:

git status
git diff --staged

確認沒問題後,再自己 commit:

git commit -m "hotfix: fix payment rounding on main"

這很適合想改一下 commit message、補少量平台差異,或把多顆小修正合成更乾淨提交的情境。

4.5 場景五:保留來源資訊,用 -x 最安心
#

如果你是在團隊協作環境,尤其是 release / stable branch 回補,拍拍君很推薦加上 -x

git cherry-pick -x 7b3d412

這會在新的 commit message 後面自動加上:

(cherry picked from commit 7b3d412...)

它的好處很直接:日後查 log 時知道來源、reviewer 比較容易追到原始討論、backport 記錄更清楚。如果你的團隊常常要在 mainreleasehotfix 之間回補修正,-x 幾乎可以當預設。

4.6 場景六:套用時順手編輯 commit 訊息
#

如果你想在 cherry-pick 當下改 commit message:

git cherry-pick -e <commit>

Git 會打開編輯器,讓你調整提交訊息。這很適合原分支上的 commit message 太內部、太簡寫,或者你搬到另一條 branch 時想改成更符合版本線的描述。例如原本在 feature branch 上只叫 fix payment bug,搬到 production hotfix branch 時,你可能更想寫成 hotfix: correct payment rounding for production checkout

五. 衝突處理與風險控制:真的卡住時,不要硬推
#

5.1 cherry-pick 也會衝突,而且很正常
#

很多人以為只挑一顆 commit,應該就不太會衝突。其實不一定。只要目前分支跟來源分支的上下文差很多,哪怕只搬一顆,也一樣可能撞到。例如同一個函式在兩邊都被改過、檔名已經改掉、原 commit 依賴另一顆尚未搬過來的前置修改,這些都很常見。

當衝突發生時,Git 通常會停下來,顯示類似訊息:

error: could not apply 7b3d412... fix rounding bug in total amount

這時候先別 panic。正常流程是:

git status

看哪些檔案衝突,手動修完後:

git add <resolved-files>
git cherry-pick --continue

5.2 不想繼續了,就 abort
#

如果你發現這顆 commit 依賴太多前置改動、衝突大到不值得手修,或者你其實挑錯 commit 了,可以直接取消:

git cherry-pick --abort

它會把工作目錄恢復到 cherry-pick 開始前的狀態。這跟 rebase --abort 一樣,是非常重要的保命按鈕。不要在一團混亂時硬推,先退,再重看歷史,通常比你帶著半殘狀態繼續衝安全很多。

5.3 如果只是想先停著,還有 quit
#

某些情況你可能不想完全回復,只是想退出 cherry-pick 流程。這時可以看看:

git cherry-pick --quit

--abort 會嘗試回到開始前的狀態,--quit 則是結束 sequencer 狀態,但保留目前工作樹內容。實務上,拍拍君最常用的還是 --continue--abort,不過知道 --quit 的存在,遇到複雜狀況時會更有餘裕。

5.4 小心「看起來能 pick,實際上邏輯不完整」
#

這是最常見的隱性風險。有些 commit 在 diff 上看起來很獨立,但實際上依賴前面某個 refactor 或資料結構調整。例如:

A: rename PaymentResult fields
B: fix timeout handling

如果你只 pick B,表面上可能套得進去,但執行時就炸了,因為欄位名稱已經對不上。所以 cherry-pick 之前,除了看 git show,也要問自己:這顆 commit 有沒有隱含依賴?它是不是建立在某次 refactor 之上?我要搬過去的 branch,結構真的一致嗎?

5.5 避免重複修正:不是能 pick 就應該 pick
#

如果某顆修正其實之後會透過 merge 正式進來,你就要考慮是否值得先 cherry-pick。因為未來 merge 時,雖然 Git 常常能辨認相似內容,但歷史層面上還是可能讓人困惑:blame 不好追、log 看起來像重複修了同一個 bug、reviewer 看到兩顆近似 commit 不知道差別在哪。

所以拍拍君的建議是:緊急回補很適合 cherry-pick;長期同步則優先考慮 merge、rebase 或更正規的 backport 流程。

5.6 cherry-pick merge commit 時要格外小心
#

大多數情況下,你不太會直接 pick merge commit。因為 merge commit 有多個 parent,Git 不知道你想以哪一邊當主要基底。如果真的要 pick merge commit,通常要指定 -m

git cherry-pick -m 1 <merge-commit>

-m 1 的意思是選第一個 parent 當 mainline。但這種操作比一般 cherry-pick 複雜很多;如果你沒有很清楚這顆 merge commit 的來源結構,拍拍君真心建議先不要。通常更安全的做法是:找到真正需要的那幾顆普通 commit,逐顆挑過來。

六. 進階實戰:release 回補、backport、批次搬運
#

6.1 經典案例:把 main 的修正 backport 到 release branch
#

假設你有這些分支:main 持續開發,release/1.4 穩定維護。某天你在 main 修了一個安全性 bug,但 release/1.4 也要補,流程通常是這樣:

git switch release/1.4
git log --oneline main
git cherry-pick -x <security-fix-commit>

修完如果有衝突,測試後再 push:

git push origin release/1.4

這種情境下,-x 幾乎是必開,因為過幾個月後,你一定會很感謝當時的自己有留下來源紀錄。

6.2 經典案例:hotfix 先上 production,再回補 main
#

另一種很常見的流程是反過來:你在 production hotfix branch 上先救火,正式上線後,這顆修正也應該回補到 main,避免之後新版本把 bug 帶回來。例如:

git switch hotfix/payment-rounding
# 修 bug

git commit -m "hotfix: correct payment rounding"

上線後回補:

git switch main
git cherry-pick -x <hotfix-commit>

這個流程很合理,因為 hotfix branch 往往很短、目的很單純。比起整條 branch merge 回來,精準挑修正通常更乾淨。

6.3 批次搬運前,先用 log 做清單
#

如果你打算回補 3 到 5 顆 commit,拍拍君不建議憑記憶直接敲。先整理清單:

git log --oneline --no-merges main --grep='fix\|hotfix'

或搭配檔案路徑篩選:

git log --oneline main -- src/payment src/checkout

確認完之後,再依序 pick:

git cherry-pick -x a1b2c3d e4f5a6b 91ac44e

這會比你一邊翻 log 一邊亂貼 hash 穩得多。

6.4 如果你需要先 review 再 commit,用 -n 很好用
#

有些團隊流程是先把幾顆修正套進 release branch,確認測試通過後,再整理成一顆版本修正提交。這時可以這樣做:

git switch release/1.4
git cherry-pick -n a1b2c3d
git cherry-pick -n e4f5a6b
git cherry-pick -n 91ac44e

接著檢查 staged 內容:

git diff --staged

最後一次 commit:

git commit -m "release: backport checkout fixes for 1.4.3"

這種方式的優點是版本修正紀錄集中,缺點則是你失去原本一顆顆 commit 的粒度。要不要這樣做,就看你們團隊比較重視哪一種可追蹤性。

6.5 想確認某顆 commit 是否已經等價存在,可先比 diff
#

有時你不確定某個修正是不是早就被手動改過,可以先看 patch:

git show <commit>

或比較分支差異:

git diff release/1.4..main -- src/payment

如果內容其實已經存在,就不要為了「形式上補流程」再 pick 一次。版本控制最怕的,不是少打一個指令,而是做了讓歷史更難懂的多餘操作。

七. 什麼時候不該用 cherry-pick
#

拍拍君很喜歡這個指令,但也想幫它洗刷一個冤名:它不是所有分支問題的答案。以下幾種情況,通常不建議優先用 cherry-pick。

7.1 你其實要的是整段功能,不是局部修補
#

如果某個 feature branch 已經完整,也準備好進主線,那正常做法通常是 merge 或 squash merge。這時候硬把裡面的 commit 一顆顆 cherry-pick,只會讓流程變繞。

7.2 你想長期雙向同步兩條 branch
#

如果兩條 branch 長期都會互相補修正,只靠 cherry-pick 很容易把歷史搞成手工 patch 地獄。這時應該回頭設計分支策略,而不是指望每次都靠人工挑 commit 維持秩序。

7.3 你根本還沒確認 commit 依賴
#

如果你看到一顆 commit 標題很誘人,就直接 pick,很容易發生 build 過不了、測試掛掉,或者程式能跑但邏輯微妙出錯。先看 git show,必要時連前後 commit 一起看;真的不確定,寧可晚五分鐘判斷,也不要早五秒製造新坑。

結語:把它當手術刀,不要當鏟土機
#

git cherry-pick 最迷人的地方,就是精準。你可以只拿一顆 bug fix、只回補一段 hotfix、只把某幾個經過驗證的 commit 帶到穩定分支;在救火、回補、backport 這些情境裡,它真的非常好用。

但也正因為它很精準,你更需要知道自己在切哪裡。拍拍君自己的使用心法很簡單:先看 log,再 pick;能加 -x 就加 -x;有衝突先停,別亂推;不要拿它當長期同步機制。如果你能把這幾句記住,git cherry-pick 幾乎就不太會從神器變成事故現場。

接下來如果你想把 Git 工作流整套補齊,很推薦把下面幾篇一起看:branch 怎麼切、rebase 什麼時候用、stash 什麼時候收現場。把這些工具放在正確位置,你的 repo 真的會乾淨很多。

延伸閱讀
#

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

相關文章

Git stash 實戰:暫存工作現場、切換任務與 patch 管理完全攻略
·11 分鐘· loading · loading
Git Git-Stash Patch Workflow Version-Control
Git rebase 完全攻略:整理 commit 歷史、互動式 rebase 與衝突處理
·11 分鐘· loading · loading
Git Rebase Interactive-Rebase Commit-History Version-Control
Git Branch 策略完全攻略:feature branch、main、hotfix 與協作流程
·11 分鐘· loading · loading
Git Branch Workflow Feature-Branch Hotfix
GitHub Actions 進階:Matrix Build + 自動發佈 PyPI
·5 分鐘· loading · loading
Github-Actions Cicd Pypi Matrix-Build Python
Github Actions: 自動化你的工作流
·5 分鐘· loading · loading
版本控制 Github Git
GitHub 指令工具
·2 分鐘· loading · loading
版本控制 Git 指令模式 Github