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

Git Branch 策略完全攻略:feature branch、main、hotfix 與協作流程

·11 分鐘· loading · loading · ·
Git Branch Workflow Feature-Branch Hotfix
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
版本控制: Git - 本文屬於一個選集。
§ 6: 本文

一. 前言:真正需要管理的不是 branch,而是混亂
#

很多人剛學 Git 時, 最先記住的是 addcommitpush。 等到專案開始多人協作, 才會發現 branch 才是真正決定 repo 健康度的地方。

你可能看過這些場景:

  • 所有人都直接往 main
  • 功能做到一半才想到要切 branch
  • 緊急修 bug 時,把半成品一起帶進正式環境
  • PR 大到 reviewer 根本不想看
  • repo 裡躺著一堆兩個月前的殭屍分支

這些問題的核心, 不是 Git 指令不夠熟。 而是 branch strategy 沒有先講清楚。

一套好的 branch 策略, 應該回答這幾個問題:

  • 哪個分支代表可部署版本?
  • 新功能從哪裡切出去?
  • 緊急修正時該走哪條路?
  • PR 應該如何合併才不會讓歷史失控?

今天這篇, 拍拍君想用最實用的版本來講。 不從過度複雜的企業流程開始, 而是從多數團隊最夠用的模型出發: mainfeature/*hotfix/*

如果你還沒熟悉 Git 基礎, 可以先看拍拍君之前寫的 Git 入門。 如果你想把分支策略接上 CI/CD, 之後再搭配 GitHub Actions 入門GitHub Actions 進階 會很順。

二. 安裝與基本設定:先把預設整理好
#

分支策略本身不是安裝教學, 但很多混亂其實從本機預設開始。 有人新 repo 預設是 master, 有人是 main; 有人 pull 習慣 rebase, 有人是 merge。

如果你希望團隊規則穩定, 最起碼先把自己的 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"

把預設主分支改成 main

git config --global init.defaultBranch main

這個設定很小, 但對團隊一致性很重要。 你的文件、腳本、CI 設定, 都會因此少掉很多分支名稱差異。

拍拍君還會順手加上這幾個:

git config --global fetch.prune true
git config --global rerere.enabled true
git config --global pull.rebase false

它們的作用分別是:

  • fetch.prune true:遠端刪掉的 branch, 本機追蹤也會順便清理
  • rerere.enabled true: Git 會記住你處理過的衝突方式
  • pull.rebase false: 預設 git pull 先走 merge 模式, 對多數初學者更直觀

可以用這個指令檢查:

git config --global --list | rg 'user|init.defaultBranch|fetch.prune|rerere|pull.rebase'

設定不是 branch strategy 的全部, 但它是很好的起跑點。 如果連主分支名稱都不統一, 後面流程通常也很難乾淨。

三. 最小可用模型:main 穩定,feature/* 短命
#

對多數團隊來說, 最實用的 branch strategy 不一定是 Git Flow。 很多時候, 下面這套就已經很夠用:

  • main:永遠保持可發布、可回滾、可信任
  • feature/*:每個功能一條 branch,做完就合併
  • hotfix/*:線上出事時,從 main 切出修正

你可以把它想成這樣:

main
 ├─ feature/login-page
 ├─ feature/payment-api
 └─ hotfix/session-timeout

這裡最重要的概念只有一個: main 不是工作台,main 是可交付狀態。

也就是說, 你在 main 上看到的內容, 應該是隨時可以拿去部署、打 tag、回滾的。 如果團隊把半完成的內容直接丟進 main, 那 main 就失去了「主幹」該有的意義。

3.1 建一個示範 repo
#

我們先用一個小專案感受這個流程。

mkdir branch-demo
cd branch-demo
git init
echo '# Branch Demo' > README.md
git add README.md
git commit -m 'chore: initialize repository'

現在看分支:

git branch

你應該會看到:

* main

3.2 新功能永遠從 main 切出去
#

假設今天要做登入頁面, 請不要直接在 main 上開始寫。 標準手勢應該是:

git switch main
git pull origin main
git switch -c feature/login-page

接著才開始開發:

mkdir -p src
echo 'console.log("login page")' > src/login.js
git add src/login.js
git commit -m 'feat: add initial login page'

如果你又補了表單驗證, 可以再切一個小 commit:

echo 'console.log("validate input")' >> src/login.js
git add src/login.js
git commit -m 'feat: add login form validation'

這樣做的好處很直接:

  • main 不會被半成品污染
  • 每個功能的變更範圍清楚
  • PR 開出來時比較好 review
  • 日後要回滾也比較容易定位

3.3 為什麼 feature branch 要短命?
#

很多 repo 的問題不是 branch 太少, 而是 branch 活太久。 一條功能分支如果拖兩週、三週, 通常就會開始出現這些症狀:

  • main 的差距越來越大
  • 合併衝突越積越多
  • PR 變成超大包,沒人想 review
  • 作者自己都忘了某段改動原本為什麼存在

所以拍拍君的建議是:

  • 一條 feature branch 只做一件事
  • 最好能在 1 到 3 天內完成
  • 功能太大時,拆成多個可逐步合併的小 PR

branch 的價值, 不是讓你把所有事塞進同一條支線。 而是讓你把工作切成可管理、可合併、可驗證的小單位。

3.4 branch 命名不要靠通靈
#

如果分支名稱只有作者自己看得懂, 團隊協作時就會很痛苦。 拍拍君偏好的命名格式是:

feature/<topic>
fix/<topic>
hotfix/<topic>
chore/<topic>
refactor/<topic>

例如:

feature/login-page
feature/oauth-github
fix/avatar-cache-bug
hotfix/payment-timeout
refactor/user-service-split

如果團隊平常會搭配 issue 編號, 也可以改成這樣:

feature/142-login-page
hotfix/317-session-null

命名規則沒有唯一真理, 但整個團隊必須一致。 規則一旦統一, PR 列表、CI 腳本、文件說明都會輕鬆很多。

四. feature/* 的完整生命週期:從切出到刪除
#

很多文章只說「切分支、合回來」。 真正容易出問題的, 反而是中間那段日常節奏。

拍拍君比較推薦的流程是:

  1. 從最新的 main 切出 feature/*
  2. 小步 commit
  3. 定期同步 main
  4. 開 Pull Request
  5. review 通過後合併
  6. 刪除 branch

4.1 切 branch 前,先同步主幹
#

不要從一個過期的 main 切出新 branch。 請先更新:

git switch main
git pull origin main
git switch -c feature/oauth-github

這個習慣可以幫你減少很多沒必要的衝突。 如果你從三天前的 main 切出去, 那你的 feature branch 一開始就已經落後。

4.2 開發途中,要定期把 main 帶進來
#

假設你做功能做到一半, 團隊另外兩個 PR 已經合進 main。 這時不要假裝沒看到。

常見同步方式有兩種。 第一種是 merge:

git switch feature/oauth-github
git fetch origin
git merge origin/main

第二種是 rebase:

git switch feature/oauth-github
git fetch origin
git rebase origin/main

怎麼選? 拍拍君的建議很務實:

  • 團隊剛起步、對 rebase 不熟: 先用 merge,同步成本低
  • 想保留線性歷史、作者自己很熟 Git: 合併前可用 rebase 整理
  • 不熟 rebase 時: 不要在共享 branch 上亂改歷史

也就是說, rebase 很強, 但不是每個團隊都要第一天就全員上手。 先把節奏跑順, 比一開始就追求漂亮歷史更重要。

4.3 開 PR 前,把 commit 稍微整理一下
#

如果你的 commit 長這樣:

wip
fix
try again
final
final2

那 reviewer 很難快速看懂你真正做了什麼。 比較理想的訊息是:

feat: add GitHub OAuth login flow
feat: persist oauth state in session
test: add callback route integration tests
docs: document local oauth environment variables

如果這條 branch 只有你自己在用, 可以在開 PR 前用互動式 rebase 稍微整理:

git rebase -i origin/main

不需要為了「歷史漂亮」硬修到神經緊繃, 但至少讓 commit 反映出真正的邏輯單位。 未來 debug 或追蹤問題時, 你會非常感謝現在的自己。

4.4 合併後一定要刪 branch
#

很多 repo 的分支列表之所以難看, 不是因為開太多 branch, 而是因為做完之後沒人清。

合併完成後, 記得清掉本機與遠端分支:

git branch -d feature/oauth-github
git push origin --delete feature/oauth-github

如果你有開 fetch.prune, 之後同步時本機追蹤資料也會跟著整理。

短命 branch 的最後一環, 就是讓它真的離場。

五. hotfix/* 才是救火線:修 production 不要順手夾帶半成品
#

很多團隊在壓力大的時候, 最容易做錯的就是 hotfix 流程。 看似只是「快點修」, 實際上卻把還沒完成的內容一起推上線。

想像一個情境。 目前正式環境跑的是昨天從 main 部署的版本。 同時有人正在 feature/payment-refactor 裡大改付款邏輯, 還沒有做完。 這時線上突然出現 session 過期 bug, 使用者無法正常結帳。

如果你直接在那條重構分支上修, 再想辦法一起 merge 回來, 就有機會把未完成的付款重構也帶進 production。 這不是 hotfix, 這是把火場跟施工現場綁在一起。

正確做法是: 從可信任的 main 切出 hotfix/*

git switch main
git pull origin main
git switch -c hotfix/session-timeout

然後只做最小修正:

vim src/session.js
git add src/session.js
git commit -m 'fix: handle expired session refresh correctly'

修好後開 PR 回 main, review、合併、部署。 必要時可以順手打 tag:

git tag v1.2.3
git push origin v1.2.3

5.1 hotfix merge 完,別忘了同步回進行中的分支
#

這一步非常多人漏掉。 如果 hotfix 已經進 main, 但正在開發的 feature branch 沒有同步最新主幹, 那幾天後這條 branch merge 回來時, 舊 bug 可能會復活。

所以 hotfix 完成後, 正在進行中的功能分支要記得同步:

git switch feature/payment-refactor
git fetch origin
git merge origin/main

或是:

git rebase origin/main

真正完整的 hotfix 流程, 不只是在 production 救火, 也要避免舊分支把火再帶回來。

5.2 什麼時候需要 release/*
#

拍拍君的答案很誠實: 多數小團隊其實不需要。

如果你們的節奏是:

  • PR 合進 main
  • CI 通過
  • 很快就能部署

main + feature + hotfix 通常就夠用了。

release/* 比較適合這些情境:

  • 你們有明確發版凍結期
  • 需要維護多個已發布版本
  • QA 驗證流程很重
  • 發版與開發節奏分得很開

如果團隊規模還小, 先不要急著把流程做厚。 太多分支模型, 很多時候不是更專業, 只是更容易讓人忘記哪條才是真的主幹。

六. 團隊協作流程:branch 規則要跟 PR 規則一起設計
#

branch strategy 不是只有命名規則。 它必須跟 Pull Request、review、保護設定一起運作。

拍拍君會把協作規則拆成三層。

6.1 第一層:團隊口頭規則
#

先定幾條最基本也最能落地的:

  • 不直接 push 到 main
  • 一個需求或 bugfix 對應一條 branch
  • PR 標題要能看懂做了什麼
  • PR 太大就拆小
  • 合併前至少要有基本測試結果

這些規則看起來很普通, 但很多團隊其實連這五條都沒形成共識。 如果你現在 repo 很亂, 往往不是因為技術太難, 而是因為連最基本的約定都沒固定。

6.2 第二層:讓平台幫你守門
#

如果你用 GitHub、GitLab 或 Bitbucket, 請盡量把 main 保護起來。 常見設定包括:

  • 禁止直接 push
  • 禁止 force push
  • 必須透過 Pull Request 合併
  • 至少一位 reviewer approve
  • 狀態檢查通過後才能 merge
  • 分支同步最新 main 後才能 merge

這些不是不信任同事, 而是把規則從「靠記憶遵守」 升級成「系統自動守門」。

如果你之後把 CI 接上來, branch strategy 的價值會更明顯。 例如:

  • push 到 feature branch 自動跑 lint
  • PR 自動跑單元測試
  • merge 到 main 再觸發部署

這時 branch 不只是工作切分工具, 也變成自動化流程的控制點。

6.3 第三層:決定 PR 要怎麼合進 main
#

Pull Request 的 merge 方式, 會直接影響歷史可讀性。 常見有三種。

Merge commit
#

main ----o----o---------M
          \            /
feature     o----o----o

優點:

  • 保留 branch 的存在痕跡
  • 對多 commit PR 很直觀
  • 不改寫原始 commit SHA

缺點:

  • 歷史圖比較分岔
  • 長期看起來可能偏花

Squash merge
#

main ----o----o----S
feature     o----o----o

優點:

  • main 很乾淨
  • 一個 PR 對應一個 commit
  • 回滾通常比較直觀

缺點:

  • branch 上細碎但有價值的 commit 會被壓平
  • 如果你很重視 commit 細節,資訊會少一點

Rebase and merge
#

main ----o----o----o----o----o
feature         o----o----o

優點:

  • 歷史最線性
  • git log 很舒服

缺點:

  • 需要大家比較熟 Git
  • 不小心時會改寫共享歷史

拍拍君自己的建議是:

  • 小團隊、追求簡潔:先用 squash merge
  • 想保留 branch 脈絡:可用 merge commit
  • 團隊 Git 熟悉度高:再考慮 rebase and merge

沒有絕對標準答案。 真正重要的是整個團隊一致。

6.4 三種常見模型怎麼選?
#

模型 分支結構 適合情境 代價
Trunk-Based main + 極短 branch 高頻部署、成熟 CI 團隊 需要很強測試與 feature flag 習慣
Main + Feature + Hotfix mainfeature/*hotfix/* 多數中小團隊 規則必須穩定執行
Git Flow maindeveloprelease/*hotfix/*feature/* 發版重、維護多版本產品 複雜度高,新人容易迷路

如果你現在還在猶豫該用哪一種, 拍拍君通常會建議先選第二種。 先把簡單規則跑順, 再決定是否真的需要更厚重的流程。

七. 一次走完整個實戰:功能開發、hotfix、再同步
#

光看原則很容易抽象, 我們來模擬一個比較像真實團隊的情境。

現在有兩個人: 拍拍君跟拍拍醬。 repo 的 main 目前穩定。 今天同時發生兩件事:

  • 拍拍君要做通知中心
  • 拍拍醬要修頭像快取 bug

7.1 拍拍君開功能 branch
#

git switch main
git pull origin main
git switch -c feature/notification-center

然後切幾個小 commit:

git add src/notifications/model.ts
git commit -m 'feat: add notification domain model'

git add src/notifications/api.ts
git commit -m 'feat: add notification fetch API'

git add src/notifications/view.tsx
git commit -m 'feat: add notification center UI'

此時功能 branch 很清楚, 就是專門承載通知中心這件事。

7.2 同時,拍拍醬從 main 開 hotfix
#

git switch main
git pull origin main
git switch -c hotfix/avatar-cache-bug

修完後提交:

git add src/avatar/cache.ts
git commit -m 'fix: invalidate avatar cache after profile update'

這個 PR merge 回 main 後, production 問題就能先被處理。 而且因為 hotfix 是從 main 切出, 所以不會夾帶通知中心的半成品。

7.3 功能 branch 要同步最新 main
#

hotfix merge 完後, 拍拍君的 branch 不能當作沒看到。 他應該同步最新主幹:

git switch feature/notification-center
git fetch origin
git merge origin/main

如果團隊偏好 rebase, 也可以是:

git rebase origin/main

這一步的價值在於: 把衝突提早到 branch 還小的時候解掉。 不要等到 PR 最後一刻, 才發現自己已經跟 main 差了好幾十個 commit。

7.4 PR 描述也很重要
#

好的 branch strategy, 通常也伴隨好的 PR 習慣。 例如你可以這樣寫描述:

## Summary
- 新增通知中心資料模型
- 新增 API 讀取邏輯
- 新增通知中心畫面

## Testing
- 手動測試通知列表載入
- 單元測試通過

## Notes
- 已同步 main 中 avatar cache hotfix

這樣 reviewer 很快就能知道: 你做了什麼、怎麼驗證、 以及這條 branch 與主幹目前的關係。

八. 常見陷阱:repo 變亂通常不是因為 Git 太難
#

最後整理幾個最常見的翻車點。

8.1 直接在 main 開工,想說等等再整理
#

這是最常見也最危險的習慣之一。 因為你一旦在 main 上累積了幾個半完成修改, 後面再切 branch 時, 很容易把不相干的變更一起帶走。

最好的防呆不是意志力, 而是固定手勢:

git switch main
git pull origin main
git switch -c feature/your-task

先切 branch 再開始寫, 會乾淨很多。

8.2 一條 branch 做太多事
#

今天本來要修登入頁, 結果順手改了 API client、 又順手整理 lint 設定、 最後連 CI 參數都改了。

這種 branch 的典型症狀是:

  • PR 很大
  • reviewer 很慢
  • merge 後出事很難回滾

如果你發現 branch 已經長出多個主題, 拍拍君建議直接拆。 核心功能一條、 純重構一條、 工具調整另一條。

8.3 feature branch 放太久不更新
#

這通常是衝突地獄的起點。 如果 main 每天都在動, 你的 branch 又一週以上不同步, 那最後的合併成本幾乎一定會上升。

比較好的做法是:

  • 每天或隔天同步一次 main
  • 先開 draft PR,提早讓團隊看到方向
  • 功能太大就拆成多個子 PR

8.4 隨手加一條 develop,但沒人真的知道怎麼用
#

有些團隊會覺得: 「我們是不是也該有 develop,看起來比較專業?」

如果你們真的有清楚規則, 例如 main 對正式環境、 develop 對整合測試環境、 還有明確 release cut 時點, 那當然可以。

但如果只是因為別人有, 自己也想加一條, 通常最後只會變成:

  • develop 常常壞掉
  • 大家不知道該 merge 去哪裡
  • 最後還是手動把東西搬回 main

複雜流程不是成熟的同義詞。 能長期執行的簡單流程, 通常更有價值。

8.5 只靠口頭約定,不開 branch protection
#

口頭約定最大的问题, 就是它會在最忙的那天失效。

所以只要 repo 稍微重要, 就盡量把這些自動化:

  • main 禁止直接 push
  • main 禁止 force push
  • PR 必須通過 CI
  • 至少一位 reviewer approve

工具不是拿來增加官僚, 而是拿來避免人類在高壓下走捷徑。

結語:branch strategy 的目標,是讓團隊穩定前進
#

Git 分支很強, 但 branch strategy 真正的價值, 從來不是把圖畫得很花。 而是讓每一條 branch 都有清楚角色, 讓每一個人都知道現在該走哪條路。

如果你現在正想整理團隊流程, 拍拍君建議先把下面三件事做好:

  • main 永遠保持可信任
  • 每個工作項目走自己的 feature/*
  • production 修復一律從 mainhotfix/*

等這套跑順了, 再慢慢把 rebase、release branch、 feature flag、CI gate 等進階做法接進來。 順序不要反過來。

工程流程不是名詞越多越成熟。 而是大家能不能每天照著同一套規則穩定前進。 如果你今天就想開始改善, 最簡單的一步就是: 把分支命名規則寫進 CONTRIBUTING.md, 再把 main 的 protection 打開。 未來很多麻煩, 真的會少一大截。

延伸閱讀
#

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

相關文章

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
版本控制軟體 Git - 安裝篇
·3 分鐘· loading · loading
版本控制 Git
Python difflib 實戰:文字差異比對、相似度比較與 patch 輸出完全攻略
·10 分鐘· loading · loading
Python Difflib Text-Processing Developer-Tools Cli
Python prompt_toolkit 實戰:打造互動式 CLI、Auto-Completion 與 REPL 完全攻略
·10 分鐘· loading · loading
Python Prompt_toolkit Cli REPL Developer-Tools