規範 Git 工作流程實務。在進行任何程式碼變更時使用。在提交、分支、解決衝突,或需要跨多個平行串流組織工作時使用。在發行版本、選擇語意化版本號、標籤或撰寫變更日誌時使用。
Git 工作流程與版本管理
概述
Git 是你的安全網。將提交視為存檔點,分支視為沙盒,歷史紀錄視為文件。當 AI 代理以高速產生程式碼時,嚴謹的版本控制是讓變更保持可管理、可審查且可復原的機制。
使用時機
永遠。每次程式碼變更都應透過 Git 進行。
核心原則
主幹開發(推薦)
保持 main 隨時可部署。在短期功能分支上工作,並在 1-3 天內合併回主線。長期開發分支是隱藏成本——它們會分歧、產生合併衝突,並延遲整合。DORA 研究一致顯示主幹開發與高效能工程團隊相關。
main ──●──●──●──●──●──●──●──●──●── (隨時可部署)
╲ ╱ ╲ ╱
●──●─╱ ●──╱ ← 短期功能分支 (1-3 天)
這是建議的預設方式。使用 Gitflow 或長期分支的團隊可以將原則(原子提交、小變更、描述性訊息)調整到他們的分支模型中——提交紀律比特定分支策略更重要。
- 開發分支是成本。 分支每存活一天,就累積更多合併風險。
- 發行分支是可接受的。 當你需要穩定一個發行版本,同時 main 繼續前進時。
- 功能開關 > 長分支。 偏好將未完成的工作透過功能開關部署,而不是在分支上保留數週。
1. 及早提交,頻繁提交
每個成功的增量都應有自己的提交。不要累積大量未提交的變更。
工作模式:
實作片段 → 測試 → 驗證 → 提交 → 下一個片段
不要這樣:
實作所有東西 → 希望它正常 → 巨大提交
提交是存檔點。如果下一個變更破壞了某個東西,你可以立即還原到最後一個已知良好的狀態。
2. 原子提交
每個提交只做一個邏輯上的事情:
# 好:每個提交是自包含的
git log --oneline
a1b2c3d 新增任務建立端點與驗證
d4e5f6g 新增任務建立表單元件
h7i8j9k 連接表單與 API 並加入載入狀態
m1n2o3p 新增任務建立測試(單元 + 整合)
# 壞:所有東西混在一起
git log --oneline
x1y2z3a 新增任務功能、修正側邊欄、更新依賴、重構工具函式
3. 描述性訊息
提交訊息解釋為什麼,而不只是做了什麼:
# 好:解釋意圖
feat: 在註冊端點加入電子郵件驗證
防止無效的電子郵件格式進入資料庫。
在路由處理層級使用 Zod schema 驗證,
與 auth.ts 中現有的驗證模式一致。
# 壞:描述從 diff 就能看出的事
更新 auth.ts
格式:
<類型>: <簡短描述>
<選擇性內文,解釋為什麼,而非做了什麼>
類型:
feat— 新功能fix— 錯誤修正refactor— 既非修正錯誤也非新增功能的程式碼變更test— 新增或更新測試docs— 僅文件chore— 工具、依賴、設定
4. 保持關注點分離
不要將格式化變更與行為變更混在一起。不要將重構與功能混在一起。每種變更類型應為單獨的提交——理想情況下也是單獨的 PR:
# 好:關注點分離
git commit -m "refactor: 將驗證邏輯提取到共用工具"
git commit -m "feat: 在註冊中加入電話號碼驗證"
# 壞:關注點混合
git commit -m "重構驗證並新增電話號碼欄位"
將重構與功能工作分開。 重構變更和功能變更是兩種不同的變更——請分別提交。這使得每個變更更容易審查、還原,並在歷史中理解。小型清理(如重新命名變數)可以在審查者酌情下包含在功能提交中。
5. 控制變更規模
目標是每個提交/PR 約 100 行。超過約 1000 行的變更應拆分。請參閱 code-review-and-quality 中的拆分策略,了解如何分解大型變更。
~100 行 → 易於審查,易於還原
~300 行 → 對單一邏輯變更可接受
~1000 行 → 拆分為較小的變更
分支策略
功能分支
main (隨時可部署)
│
├── feature/task-creation ← 每個分支一個功能
├── feature/user-settings ← 平行工作
└── fix/duplicate-tasks ← 錯誤修正
- 從
main(或團隊的預設分支)建立分支 - 保持分支短期(1-3 天內合併)——長期分支是隱藏成本
- 合併後刪除分支
- 偏好使用功能開關而非長期分支來處理未完成的功能
分支命名
feature/<簡短描述> → feature/task-creation
fix/<簡短描述> → fix/duplicate-tasks
chore/<簡短描述> → chore/update-deps
refactor/<簡短描述> → refactor/auth-module
使用 Worktrees
對於平行 AI 代理工作,使用 git worktrees 同時執行多個分支:
# 為功能分支建立 worktree
git worktree add ../project-feature-a feature/task-creation
git worktree add ../project-feature-b feature/user-settings
# 每個 worktree 是獨立的目錄,有自己的分支
# 代理可以平行工作而不互相干擾
ls ../
project/ ← main 分支
project-feature-a/ ← task-creation 分支
project-feature-b/ ← user-settings 分支
# 完成後,合併並清理
git worktree remove ../project-feature-a
優點:
- 多個代理可以同時處理不同功能
- 無需切換分支(每個目錄有自己的分支)
- 如果某個實驗失敗,刪除 worktree——不會遺失任何東西
- 變更在明確合併前是隔離的
存檔點模式
代理開始工作
│
├── 進行變更
│ ├── 測試通過?→ 提交 → 繼續
│ └── 測試失敗?→ 還原到最後一次提交 → 調查
│
├── 進行另一個變更
│ ├── 測試通過?→ 提交 → 繼續
│ └── 測試失敗?→ 還原到最後一次提交 → 調查
│
└── 功能完成 → 所有提交形成乾淨的歷史
這個模式意味著你永遠不會遺失超過一個增量的工作。如果代理偏離軌道,git reset --hard HEAD 會帶你回到最後一個成功狀態。
變更摘要
在任何修改之後,提供結構化的摘要。這使得審查更容易,記錄範圍紀律,並揭露非預期的變更:
變更內容:
- src/routes/tasks.ts: 在 POST 端點加入驗證中介層
- src/lib/validation.ts: 使用 Zod 新增 TaskCreateSchema
我未觸及的部分(刻意):
- src/routes/auth.ts: 有類似的驗證缺口但不在範圍內
- src/middleware/error.ts: 錯誤格式可改進(獨立任務)
潛在疑慮:
- Zod schema 很嚴格——拒絕額外欄位。請確認這是預期行為。
- 新增 zod 作為依賴(gzip 後 72KB)——已在 package.json 中
這個模式能及早捕捉錯誤假設,並為審查者提供變更的清晰地圖。「未觸及的部分」區塊尤其重要——它顯示你執行了範圍紀律,沒有進行未經請求的翻修。
提交前衛生
每次提交前:
# 1. 檢查即將提交的內容
git diff --staged
# 2. 確保沒有機密
git diff --staged | grep -i "password\|secret\|api_key\|token"
# 3. 執行測試
npm test
# 4. 執行 lint
npm run lint
# 5. 執行型別檢查
npx tsc --noEmit
透過 Git hooks 自動化:
// package.json (使用 lint-staged + husky)
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"]
}
}
處理產生的檔案
- 提交產生的檔案僅在專案預期需要時(例如
package-lock.json、Prisma 遷移) - 不要提交建置輸出(
dist/、.next/)、環境檔案(.env)或 IDE 設定(除非共用,否則.vscode/settings.json) - 準備
.gitignore涵蓋:node_modules/、dist/、.env、.env.local、*.pem
使用 Git 除錯
# 找出哪個提交引入了錯誤
git bisect start
git bisect bad HEAD
git bisect good <已知良好提交>
# Git 會 checkout 中間點;在每個點執行測試以縮小範圍
# 檢視最近變更
git log --oneline -20
git diff HEAD~5..HEAD -- src/
# 找出最後修改特定行的人
git blame src/services/task.ts
# 搜尋提交訊息中的關鍵字
git log --grep="validation" --oneline
發行與版本管理
提交是你自己追蹤變更的方式;版本是你的消費者追蹤變更的方式。一旦有任何東西依賴你的程式碼——另一個團隊、一個發布的套件、一個部署的客戶端——「main 上的最新版本」就不再是「我正在執行什麼,升級安全嗎?」的充分答案。版本號和變更日誌是回答這個問題的合約。
語意化版本
對於有任何消費者的東西,使用 MAJOR.MINOR.PATCH 版本號,讓數字承載意義:
MAJOR 破壞性變更——消費者必須修改程式碼才能升級
MINOR 新功能,向後相容——安全升級
PATCH 錯誤修正,向後相容——安全升級
數字是一個承諾,所以讓程式碼符合它。一個「修補」版本改變了消費者依賴的行為,就是偽裝的主要變更(Hyrum's Law——請參閱 api-and-interface-design 技能)。如果不確定變更是否為破壞性,假設它是;一個意外的主要版本比一個被破壞的消費者便宜得多。
標記發行版本,讓標籤成為事實來源
發行版本是歷史中不可變的點,而不是移動的分支。標記它以便永遠可以重現:
git tag -a v1.4.0 -m "Release 1.4.0"
git push origin v1.4.0
從標籤推導版本,而不是在分散的檔案中手動編輯,這樣成品、標籤和變更日誌永遠不會不一致。
維護為人類撰寫的變更日誌
變更日誌不是 git log。它是經過策劃、面向消費者的「什麼變了,我在意嗎?」的答案——按 Added / Changed / Fixed / Deprecated / Removed / Security 分組,最新在上,每個條目圍繞使用者影響而非內部機制。
## [1.4.0] - 2025-06-12
### Added
- 透過 CSV 批量匯入任務
### Fixed
- 週期性任務截止日期的時區偏移
### Deprecated
- `GET /v1/tasks/all` — 請使用分頁的 `GET /v1/tasks`(將在 2.0 移除)
在進行變更的同一提交中撰寫條目,趁影響還新鮮——而不是在發行時從提交考古重建。破壞性變更需要遷移說明和棄用窗口(遵循 deprecation-and-migration 技能);實際發行是 shipping-and-launch 技能的工作——本節是提供給它的版本管理合約。
常見合理化藉口
| 合理化藉口 | 現實 |
|---|---|
| 「功能完成時再提交」 | 一個巨大提交無法審查、除錯或還原。每個片段都要提交。 |
| 「訊息不重要」 | 訊息就是文件。未來的你(和未來的代理)需要理解什麼變了以及為什麼。 |
| 「我之後再 squash」 | Squashing 會破壞開發敘事。偏好從一開始就保持乾淨的增量提交。 |
| 「分支增加開銷」 | 短期分支是免費的,並防止衝突的工作互相碰撞。長期分支才是問題——1-3 天內合併。 |
| 「我之後再拆分這個變更」 | 大型變更更難審查、部署風險更高、更難還原。在提交前拆分,而不是之後。 |
| 「我不需要 .gitignore」 | 直到包含生產機密的 .env 被提交。立即設定。 |
| 「只是個小修正,升 patch 就好」 | 檢查消費者能觀察到什麼。他們依賴的行為變更是 major,無論 diff 大小。 |
| 「變更日誌就是提交日誌」 | 提交是給你的;變更日誌是給消費者的,按影響策劃。從原始提交產生會埋沒重點。 |
| 「我們在發行時再寫變更日誌」 | 到那時影響是從記憶中重建的,一半都遺漏了。在變更時就寫條目。 |
紅旗
- 大量未提交的變更累積
- 提交訊息如「fix」、「update」、「misc」
- 格式化變更與行為變更混合
- 專案中沒有
.gitignore - 提交
node_modules/、.env或建置成品 - 長期分支與 main 顯著分歧
- 強制推送至共用分支
- 破壞性變更以 minor 或 patch 版本發行
- 發行版本沒有標籤,或版本號手動編輯與標籤不同步
- 面向使用者的發行沒有變更日誌條目,或變更日誌只是傾印的提交訊息
驗證
每次提交:
- [ ] 提交只做一個邏輯事情
- [ ] 訊息解釋為什麼,遵循類型慣例
- [ ] 提交前測試通過
- [ ] diff 中沒有機密
- [ ] 沒有僅格式化的變更與行為變更混合
- [ ]
.gitignore涵蓋標準排除項目
每次發行(有任何消費者):
- [ ] 版本提升符合變更:破壞性 → major,新增 → minor,修正 → patch
- [ ] 發行版本已標記,版本從標籤推導,而非手動編輯不同步
- [ ] 變更日誌有針對此版本按影響分組、經過策劃且人類可讀的條目






