Git 分支與合併策略

是什麼?(分支策略 vs 合併策略是兩個不同層次的決定)

很多人把「我們用 Git Flow」跟「我都用 rebase」混為一談,但這其實是兩個正交(orthogonal)的決定:

身為已經用過 merge / rebase / squash 的資深開發者,你其實已經在操作「合併策略」了。這一課的目標是把這兩個層次講清楚,並補齊你提到的 Azure DevOps Semi-linear merge,以及 cherry-pick、ours/theirs、--no-ff 等其他方式,每種都附優缺點。

ℹ️一句話區分

分支策略回答「我們的分支長什麼樣、活多久」;合併策略回答「合併那一刻,commit 要怎麼進主線」。同一種分支策略(例如 Trunk-Based)可以搭配不同的合併方式(例如 squash 或 semi-linear)。

分支策略比較(Git Flow、GitHub Flow、GitLab Flow、Trunk-Based Development)

下表逐項列出四種主流分支策略的優缺點與適用場景:

策略核心特徵優點缺點適用場景
Git Flow多條長命分支:maindevelopfeaturereleasehotfix角色分明、適合有明確版本號的發佈節奏;hotfix 流程清楚流程重、分支多、developmain 易分歧;不利持續部署需要維護多版本、定期發版的桌面/套裝軟體、嵌入式
GitHub Flow只有一條長命 main,其餘都是短命 feature 分支,靠 PR 合併後即部署簡單、易懂、適合持續部署;分支生命短沒有專門的 release/環境分支,多環境晉級需另外設計SaaS、Web 服務、持續部署團隊
GitLab Flow在 GitHub Flow 上加「環境分支」(如 stagingproduction)或 release 分支兼顧簡單與多環境晉級/回溯需求比 GitHub Flow 稍複雜,需要約定晉級規則有明確環境晉級流程、需追蹤哪版上了哪環境的團隊
Trunk-Based Development全部直接往單一主幹(trunk)整合,分支極短命(常 < 1 天),搭配 Feature Flag整合頻繁、衝突小、最利 CI/CD 與大型團隊需要強紀律:完整自動測試、Feature Flag、嚴格 Code Review高頻部署、成熟 CI/CD、講求快速回饋的團隊
main 主線
開分支
feature/login 短命分支
當天合回
feature/cart 短命分支
當天合回
未完成功能藏在 Feature Flag 後

💡趨勢提示

業界(尤其 DORA 報告)長期觀察到:分支愈短命、整合愈頻繁,部署效能愈好。Git Flow 雖然經典,但作者本人後來也提醒它不適合持續交付的 Web 團隊。新專案若要上 CI/CD,預設往 GitHub Flow 或 Trunk-Based 想。

合併方式詳解與比較(Merge commit、Fast-forward、Squash merge、Rebase、Rebase and merge、Semi-linear merge)

這些是你把 feature 分支縫回主線時的選項。先逐一說明,再用一張表總比較。

ℹ️Semi-linear 的記憶法

Semi-linear = 「線性的 commit + 一顆 merge commit 收尾」。沿主線看 first-parent 是一條直線,但每個 PR 仍有一個明確的 merge node 當作「這次合進來的東西」的標記。Azure DevOps 與 GitLab 都支援這種模式。它要求 feature 分支能乾淨 rebase 到主線頂端,否則會要你先解衝突。

方式保留分支歷史主線是否線性是否改寫 commit衝突處理適用情境主要風險
Merge commit完整保留否(有分叉)一次性在 merge 時解想忠實保留協作歷史、看得到分支結構歷史圖雜亂、git log 難讀
Fast-forward不保留分支痕跡無(無分歧才可 ff)個人分支、瑣碎更新看不出「曾有過分支/功能邊界」
Squash merge丟失中間 commit是(壓成一顆)在合併時一次解想要「一功能一 commit」的乾淨主線失去有用的中間歷史、難精準 bisect/revert 局部
Rebase保留每個 commit 但改寫是(換 SHA)可能要逐 commit 解想要乾淨線性歷史、整理本地 commit對已 push 的公共分支極危險
Rebase and merge保留每個 commit是(換 SHA)逐 commit 解想線性又保留每顆 commit失去 PR 邊界、衝突解起來較煩
Semi-linear merge保留每個 commit + PR 邊界first-parent 線性是(先 rebase)先 rebase 解,再 merge兼顧線性與功能邊界(Azure DevOps 推薦)設定較不直覺、需可乾淨 rebase

還有哪些方式

除了上面的「整個分支合回」之外,還有幾種針對性的操作,各有專門用途:

指令範例

bash
# 1) Rebase:把 feature 分支重放到最新 main 之後(線性歷史)
git switch feature/login
git fetch origin
git rebase origin/main
# 解完衝突後
git add .
git rebase --continue
# 推回(已 push 過的個人分支才需要,且務必用 --force-with-lease)
git push --force-with-lease
 
# 2) Squash:把整支 feature 壓成一顆 commit 合進 main
git switch main
git merge --squash feature/login
git commit -m "feat: 完成登入功能"   # squash 後需自己 commit
 
# 也可用互動式 rebase 在本地先整理/壓縮自己的 commit
git rebase -i origin/main   # 把要壓的行從 pick 改成 squash 或 fixup
 
# 3) Cherry-pick:把某個 hotfix commit 挑到目前分支
git switch release/1.4
git cherry-pick a1b2c3d            # 單一 commit
git cherry-pick a1b2c3d^..f4e5d6   # 一段範圍
 
# 4) --no-ff:強制留下功能合併的 merge commit 邊界
git switch main
git merge --no-ff feature/cart

常見誤區

⚠️對已經 push 的公共分支做 rebase

rebase 會改寫 commit 的 SHA。如果分支已經推到遠端、別人也基於它工作,rebase 後強推會讓所有人的歷史對不上、被迫硬重設,引發災難。鐵律:rebase 只用於「還沒分享出去」的本地 commit;公共分支要整合,請用 merge。真的要強推私有分支時也務必用 --force-with-lease 而非 --force,以免覆蓋別人剛推上來的東西。

⚠️無腦 squash 掉有用的中間歷史

squash 很爽,但會把分支內所有中間 commit 壓成一顆。若這條分支跨越好幾天、含多個邏輯上獨立的修改,壓成一顆會讓 git bisect 失去定位精度、也無法只 revert 其中一小段。原則:把無意義的「typo / wip / fix」雜訊壓掉,但保留邏輯上獨立、未來可能要單獨追溯或回退的 commit。

⚠️長命分支造成 merge hell

讓 feature 分支活上好幾週,是衝突地獄的根源:主線一直前進,你的分支愈漂愈遠,最後合併時要解的衝突呈指數成長,還容易夾帶語意衝突(程式碼能合但邏輯壞了)。解法是「縮短分支壽命、頻繁整合」——這正是 Trunk-Based Development 搭配 Feature Flag 的核心動機。

實戰補充(Q&A)

Q:團隊到底該選哪種分支策略? A:先看「部署頻率」與「需不需要同時維護多版本」。需要維護多個發行版本(如套裝軟體、有客戶停在舊版)→ Git Flow 或 GitLab Flow 的 release 分支有價值。只維護一條線、追求快速上線的 SaaS/Web → GitHub Flow;若 CI/CD 成熟、團隊大、想極致頻繁整合 → Trunk-Based。新專案沒包袱時,預設從 GitHub Flow 起步,再視成熟度往 Trunk-Based 演進。

Q:CI/CD 比較適合哪種分支策略? A:Trunk-Based Development + 短命分支 + Feature Flag 是與 CI/CD 最契合的組合。原因:CI 的價值來自「頻繁整合、快速回饋」,分支愈短命整合愈頻繁,衝突與整合風險就愈小;未完成的功能藏在 Feature Flag 後面,就能讓「程式碼隨時可上線」與「功能尚未對外開放」並存,達成持續部署。長命分支與 CI/CD 在哲學上是衝突的。

Q:rebase 跟 merge 到底怎麼選? A:用一句話判斷——「分支分享出去了沒?」。還沒 push、純本地整理自己的 commit → 放心 rebase,讓歷史乾淨。已是公共/共享分支的整合 → 用 merge,別改寫共享歷史。想兼顧「線性」與「保留功能邊界」→ 用 Azure DevOps 的 semi-linear merge 或 --no-ff。團隊層級則建議直接在平台設定統一的合併規則(例如一律 squash 或一律 semi-linear),別讓每個人各憑喜好。

理解測驗

🤔 關於 Azure DevOps 的 Semi-linear merge(半線性合併),下列敘述何者最正確?

🤔 哪一組合最適合搭配成熟的 CI/CD 與持續部署?

🤔 下列哪個情境最不該使用 rebase?

重點整理

💡一句話記住

分支策略決定「分支怎麼長、活多久」,合併策略決定「commit 怎麼進主線」;rebase 只動沒分享過的本地 commit、公共分支整合用 merge,而想要「乾淨線性又保留功能邊界」就選 Semi-linear merge——CI/CD 時代的最佳預設是 Trunk-Based + 短命分支 + Feature Flag。

重點一句話總結
兩個層次分支策略 ≠ 合併策略,是正交的兩個決定
分支策略選擇多版本維護選 Git/GitLab Flow;持續部署選 GitHub Flow/Trunk-Based
CI/CD 最佳拍檔Trunk-Based + 短命分支 + Feature Flag
rebase vs merge沒分享過 → rebase;公共分支整合 → merge
Semi-linear mergerebase 成線性 + 一顆 merge commit,兼顧線性與 PR 邊界
其他工具cherry-pick 挑單顆、ours/theirs 選邊、--no-ff 留邊界、--ff-only 強制線性
三大誤區公共分支 rebase、squash 掉有用歷史、長命分支造成 merge hell

你可能也想看

技術債(Technical Debt)測試類型全解

按 ← → 鍵切換課程