git-workflow-and-versioning

git-workflow-and-versioning

热门

规范 Git 工作流实践。在进行任何代码更改时使用。在提交、分支、解决冲突或需要跨多个并行流组织工作时使用。在发布版本、选择语义版本号、打标签或编写变更日志时使用。

7.8万Star
8330Fork
更新于 2026/7/12
SKILL.md
readonly只读
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 模式验证,
与 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

使用工作树

对于并行 AI 代理工作,使用 Git 工作树同时运行多个分支:

# 为特性分支创建工作树
git worktree add ../project-feature-a feature/task-creation
git worktree add ../project-feature-b feature/user-settings

# 每个工作树是一个独立的目录,拥有自己的分支
# 代理可以并行工作而不互相干扰
ls ../
  project/              ← main 分支
  project-feature-a/    ← task-creation 分支
  project-feature-b/    ← user-settings 分支

# 完成后,合并并清理
git worktree remove ../project-feature-a

好处:

  • 多个代理可以同时处理不同的功能
  • 无需切换分支(每个目录有自己的分支)
  • 如果一个实验失败,删除工作树——不会丢失任何内容
  • 更改在显式合并之前是隔离的

保存点模式

代理开始工作
    │
    ├── 进行更改
    │   ├── 测试通过?→ 提交 → 继续
    │   └── 测试失败?→ 回滚到上次提交 → 调查
    │
    ├── 进行另一个更改
    │   ├── 测试通过?→ 提交 → 继续
    │   └── 测试失败?→ 回滚到上次提交 → 调查
    │
    └── 功能完成 → 所有提交形成干净的历史

这种模式意味着你永远不会丢失超过一个增量的工作。如果代理偏离轨道,git reset --hard HEAD 将带你回到最后一个成功状态。

变更摘要

在任何修改之后,提供结构化的摘要。这使审查更容易,记录范围纪律,并暴露意外更改:

所做的更改:
- src/routes/tasks.ts: 为 POST 端点添加验证中间件
- src/lib/validation.ts: 使用 Zod 添加 TaskCreateSchema

我故意未触及的内容:
- src/routes/auth.ts: 存在类似的验证缺口但不在范围内
- src/middleware/error.ts: 错误格式可以改进(单独任务)

潜在问题:
- Zod 模式严格——拒绝额外字段。确认这是期望的行为。
- 添加了 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 钩子自动化:

// 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 检出中点;在每个点运行测试以缩小范围

# 查看最近更改的内容
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  错误修复,向后兼容——升级安全

数字是一个承诺,所以代码必须与之匹配。一个改变消费者依赖的行为的“补丁”是一个伪装成小版本的重大变更(海勒姆定律——参见 api-and-interface-design 技能)。当不确定变更是否破坏性时,假设它是;一个意外的重大版本比一个损坏的消费者代价小得多。

标记发布,让标签成为真相来源

发布是历史中不可变的点,而不是移动的分支。标记它以便始终可以重现:

git tag -a v1.4.0 -m "发布 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 技能的工作——本节是提供输入的版本契约。

常见合理化

合理化 现实
“功能完成时我再提交” 一个巨大的提交无法审查、调试或回滚。每个切片单独提交。
“消息不重要” 消息是文档。未来的你(和未来的代理)需要理解什么变了以及为什么。
“我稍后会全部压缩” 压缩会破坏开发叙事。从一开始就保持干净的增量提交。
“分支增加开销” 短期分支是免费的,并防止冲突工作碰撞。长期分支才是问题——在 1-3 天内合并。
“我稍后会拆分这个更改” 大型更改更难审查,部署风险更大,更难回滚。在提交前拆分,而不是之后。
“我不需要 .gitignore” 直到包含生产密钥的 .env 被提交。立即设置它。
“只是一个小修复,升补丁版本” 检查消费者能观察到什么。他们依赖的行为变化是重大变更,无论 diff 大小如何。
“变更日志就是提交日志” 提交是为你自己;变更日志是为消费者,按影响策划。从原始提交生成会埋没重要内容。
“我们会在发布时编写变更日志” 到那时影响是从记忆中重建的,一半都丢失了。在变更时编写条目。

红旗

  • 大量未提交的变更积累
  • 提交消息如“fix”、“update”、“misc”
  • 格式化更改与行为更改混合
  • 项目中无 .gitignore
  • 提交 node_modules/.env 或构建产物
  • 长期分支与 main 显著偏离
  • 强制推送到共享分支
  • 破坏性变更以次要或补丁版本发布
  • 发布没有标签,或版本号手动编辑与标签不同步
  • 面向用户的发布没有变更日志条目,或变更日志只是转储的提交消息

验证

对于每次提交:

  • [ ] 提交只做一件事
  • [ ] 消息解释为什么,遵循类型约定
  • [ ] 提交前测试通过
  • [ ] diff 中无机密
  • [ ] 无仅格式化的更改与行为更改混合
  • [ ] .gitignore 涵盖标准排除项

对于每次发布(任何有消费者的东西):

  • [ ] 版本号提升与变更匹配:破坏性 → major,新增 → minor,修复 → patch
  • [ ] 发布已打标签,版本从标签派生,而非手动编辑不同步
  • [ ] 变更日志有按影响分组的、经过策划的、人类可读的条目