git-workflow-and-versioning

git-workflow-and-versioning

熱門

規範 Git 工作流程實務。在進行任何程式碼變更時使用。在提交、分支、解決衝突,或需要跨多個平行串流組織工作時使用。在發行版本、選擇語意化版本號、標籤或撰寫變更日誌時使用。

7.8萬星標
8330分支
更新於 2026/7/12
SKILL.md
readonlyread-only
name
git-workflow-and-versioning
description

規範 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
  • [ ] 發行版本已標記,版本從標籤推導,而非手動編輯不同步
  • [ ] 變更日誌有針對此版本按影響分組、經過策劃且人類可讀的條目