适用读者:从"一个人提交代码"进入"多人在同一仓库协作"的人;被问过"你这个分支从哪儿拉的"的人。
前置:S01-B10(Git 入门)、S01-J01(引用与对象模型)。
实测环境:Ubuntu 24.04.1 / Git 2.43.0;commit-msg钩子拦截效果为 2026-11-05 实测。
覆盖旧文:A-555(团队 Git 开发规范:分支、Commit、MR 与 CI,543 个点位,本系列最大单篇)。
新增扩展:E-S01-016(提交签名)、E-S01-017(Conventional Commits 与 commitlint)、E-S01-018(pre-commit 钩子)。
上一篇:S01-J01 · Git 内部模型:快照、对象、引用与内容寻址
下一篇:S01-J03 · Git 手术与效率。
1. 一句话结论
规范的价值不是"让人守规矩",而是让半年后的 git log 还能读懂。 其余(分支、MR、CR、CI)都是为这一条服务的。
旧文开篇论证得很对,但只有一句"让代码修改本身具有可读性"。本篇把它拆成四个可检查的产物:
| 产物 | 可检查的标准 |
|---|---|
| Commit 消息 | 能否只看 git log --oneline 判断"改了什么、影响哪里" |
| 分支名 | 能否从名字知道它要合到哪、属于什么类型 |
| MR 描述 | 评审者能否不看代码就知道"为什么改、怎么验证" |
| CI 结果 | 合并后是否自动验证,失败是否可定位 |
2. Commit 规范:九种类型
旧文的九类划分是对的,直接沿用:
| 类型 | 含义 | 例子(旧文格式) |
|---|---|---|
feat |
新功能 | feat/multi_merchant: 支持多商户下单 |
bugfix |
普通 Bug 修复 | bugfix/user: 修复手机号为空导致登录失败 |
hotfix |
紧急线上问题 | hotfix/create_order: 修复重复创建订单 |
perf |
性能优化 | perf/user_login: 优化登录查询性能 |
style |
格式、清理,不影响业务 | style/log_print |
refactor |
重构(不改行为) | refactor/user |
test |
测试相关 | test/user |
docs |
文档、注释 | docs/user |
chore |
依赖、配置、脚手架 | chore/dependency |
2.1 格式之争:旧文的 / 与主流的 ()
| 写法 | 示例 | 生态兼容性 |
|---|---|---|
| 旧文写法(斜杠) | feat/user: 支持多商户下单 |
人类可读;commitlint / semantic-release 默认不认 |
| Conventional Commits | feat(user): 支持多商户下单 |
工具链(changelog、版本号自动生成、release)通用 |
建议:用 type(scope): 描述(括号格式)。理由不是"哪个更好看",而是:
- 你的 changelog / 版本号自动化(semantic-release、standard-version)按这个格式解析;
- GitHub/GitLab 的提交关联(
Closes #123)与类型解析都基于它; - 社区文档与工具默认值都是它。
如果你团队已经在用斜杠格式,不要混用——二选一,并写进仓库的 CONTRIBUTING.md。混用的后果是工具一半认一半不认。
V-14 记录:旧文的斜杠格式与 Conventional Commits 存在冲突,本篇按"括号格式为准、斜杠格式作为历史约定保留"处理,并把差异写进
gaps.md。
2.2 好与坏:一条判据
| 坏 | 为什么坏 | 好 |
|---|---|---|
update |
不知道改了什么 | fix(order): 修复并发下单重复创建 |
修复问题 |
同上,且没有范围 | fix(order): 修复并发下单重复创建 |
修改代码 |
等于没说 | refactor(order): 抽取订单校验到独立方法 |
fix |
缺范围与说明 | fix(user): 修复手机号为空导致登录失败 |
判据:把这条消息单独发给同事,他能否知道"改了什么、影响哪块业务"。
2.3 自动化:commit-msg 钩子(实测)
规则靠人守不住,靠钩子兜底。在仓库里放 .git/hooks/commit-msg:
#!/bin/sh
PATTERN='^(feat|bugfix|hotfix|perf|style|refactor|test|docs|chore)(\(.+\))?!?: .{4,}'
if ! grep -qE "$PATTERN" "$1"; then
echo "❌ 提交信息不符合规范:type(scope): 说明(至少 4 字)"
echo " 允许类型:feat bugfix hotfix perf style refactor test docs chore"
echo " 你的输入:$(head -1 "$1")"
exit 1
fi
实测效果:
$ git commit -m "update"
❌ 提交信息不符合规范:type(scope): 说明(至少 4 字)
允许类型:feat bugfix hotfix perf style refactor test docs chore
你的输入:update
$ git commit -m "feat(git): 增加 commit-msg 钩子校验"
[master (root-commit) 6fed6b5] feat(git): 增加 commit-msg 钩子校验
1 file changed, 1 insertion(+)
钩子不进仓库(
.git/hooks是本地目录)。团队共享要走core.hooksPath或husky/pre-commit框架(E-S01-018)。mkdir -p .githooks && cp <hook> .githooks/commit-msg git config core.hooksPath .githooks # 让仓库里的钩子生效
3. 分支模型:四个环境分支
旧文给的是一个"环境分支"模型(与 GitFlow 不同):
| 分支 | 受保护 | 作用 |
|---|---|---|
test |
✅ | 集成测试环境,日常合入门 |
pre |
✅ | 预发布,验收 |
prod |
✅ | 生产 |
master |
✅ | 线上代码存档,与 prod 同步 |
| 开发分支 | ❌ | 个人/特性分支 |
feature/* ──► test ──► pre ──► prod
│ │
└── master ◄─────┘(存档,与 prod 同步)
3.1 从哪个分支拉?(旧文最关键的一节)
| 项目状态 | 从哪拉 | 为什么 |
|---|---|---|
| 已上线 | master / prod |
线上稳定代码是唯一可信基线 |
| 未上线 | test |
此时 test 是最新集成基线 |
为什么不能从 test 拉新功能(已上线项目):test 里可能混着未验证完的他人改动。从 test 拉分支,等于把你的特性建在流沙上——出问题时无法判断是你的代码还是别人的。
3.2 分支命名与 Commit 类型一致
feat/multi_merchant hotfix/create_order
bugfix/user_login perf/user_login
反例:test2、dev123、mybranch——别人完全不知道用途,也没法判断该合到哪。
3.3 与 trunk-based 的取舍(本篇补充)
| 模型 | 适合 | 代价 |
|---|---|---|
| 环境分支模型(旧文,test/pre/prod) | 有独立测试环境、发布节奏固定、需要人工验收 | 分支多,合并链条长,test 容易积压 |
| trunk-based(主干开发 + 短分支 + 特性开关) | 高频发布、自动化测试完备 | 要求 CI 强、开关治理好 |
| GitFlow(develop/release/hotfix) | 版本化交付(客户端/私有化) | 分支最重,小团队用不动 |
判据:你的 CI 能不能在 10 分钟内给出可信的"能发"结论? 能 → 可以走 trunk-based;不能 → 老老实实用环境分支。
4. 标准开发流程(新功能)
# 1. 同步基线(已上线项目)
git checkout master && git pull
# 2. 建分支
git checkout -b feat/multi_merchant
# 3. 开发 + 小步提交
git add -p && git commit -m "feat(multi_merchant): 增加多商户下单入口"
git commit -m "feat(multi_merchant): 增加商户维度校验"
# 4. 推送
git push -u origin feat/multi_merchant
# 5. 开 MR(→ test)
# 6. CR 通过 → 合并 → CI 自动跑
# 7. 测试环境验证 → 验收通过 → 合入 pre → prod
关键纪律:
| 纪律 | 原因 |
|---|---|
| 分支存活 ≤ 3 天 | 分支越久,合并冲突越贵 |
| 一个分支只做一件事 | 混类型提交让 CR 无法判断风险 |
不直接 push 到 test(旧文第二十章) |
保护分支的意义:所有改动经 MR + CR |
| 提交粒度 ≤ 一次逻辑改动 | 出问题能 git bisect 定位(S01-J03) |
5. Merge Request / PR:写什么
旧文对 MR 的定义很清楚:"请别人评审你的代码"的正式请求。但没给模板。这里补一个够用的:
## 做了什么
- 增加多商户下单入口(`OrderController#create` 支持 merchantId)
## 为什么
- 需求:单商户 → 多商户(PRD 链接)
## 怎么验证
1. `mvn -pl order-service test`
2. 手工:用商户 A/B 各下一单,确认 `order.merchant_id` 正确
## 风险与回滚
- 风险:老客户端不传 merchantId → 默认单商户(已兼容)
- 回滚:`git revert <merge-commit>`,无数据迁移
评审者最关心的三件事:为什么改(动机)、怎么验证(证据)、出问题怎么退(回滚)。不写这三样,MR 就是"你看我代码吧",评审质量必然低。
6. Code Review 看什么(旧文第三至五章的重组)
6.1 顺序(先看大的,再看小的)
- 改动范围:是否符合 MR 描述(有没有夹带无关改动)
- 接口与契约:对外 API/DB 结构有没有变,变了是否向下兼容
- 边界与异常:空值、并发、超时、重试
- 测试:新增/修改逻辑是否有对应测试
- 命名与可读性:最后再看风格(工具能查的别让人查)
反例:一上来逐字读格式与命名,读完发现整体方案不该这么做——白花 40 分钟。
6.2 一条硬规则:CR 不能只是"看起来没问题"
| 结论 | 需要什么 |
|---|---|
| 通过 | 至少说明"我看过哪些文件、验证了哪些场景" |
| 打回 | 指出具体位置 + 期望做法,不写"再改改" |
| 不确定 | 明确写出"这块我不熟,需要 X 确认",别假装通过 |
7. CI:合并之后自动做什么
| 阶段 | 内容 | 失败意味着 |
|---|---|---|
| 构建 | mvn -B verify |
代码不能编译 → 立即打回 |
| 单元测试 | 同上(surefire) | 逻辑坏了 |
| 静态检查 | Checkstyle/SpotBugs/Sonar | 规范或潜在缺陷 |
| 打包 | 生成制品(jar/镜像) | 发布链路断了 |
| 部署到 test | 自动/半自动 | 环境问题(区别于代码问题) |
纪律:CI 失败的人负责修,不能"下次一起修"。CI 红了还往上合,等于把验证环节废掉。
8. 提测、Bug 与 Hotfix(旧文第六至八章)
8.1 提测
feature 分支 → MR → test 分支合并 → CI 部署 test → 测试同学验证
提测前自检(旧文没有,但决定提测是否被打回):
- 本地
mvn verify通过 - 新增逻辑有测试
- 数据库变更脚本已提交(且可在 test 环境重放)
- 配置项变更已同步给运维
- MR 描述里写了验证步骤
8.2 Bug 修复
git checkout master && git pull
git checkout -b bugfix/user_login
# 修复 + 提交(附复现步骤)
git commit -m "fix(user): 修复手机号为空导致登录失败"
8.3 Hotfix(线上紧急)
# 1. 从 prod(或与 prod 同步的 master)拉
git checkout prod && git pull
git checkout -b hotfix/create_order
# 2. 最小改动修复 + 提交
git commit -m "fix(order): 修复并发下单重复创建"
# 3. 先合 prod,再回合 master(防止下次发版把修复覆盖掉)
Hotfix 的两条铁律:
- 必须从 prod 拉,不能从 test 拉(否则会带上未上线代码)。
- 修完必须回合 master。只合 prod 不合 master,下次从 master 拉分支再发版时,hotfix 会被"覆盖丢失"——这是线上事故的经典复发路径。
9. 提交签名(E-S01-016)
# SSH 签名(Git 2.34+,比 GPG 简单)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git log --show-signature -1 # 验证
git verify-commit HEAD # 显式验证
| 场景 | 是否值得开 |
|---|---|
| 开源项目接受外部贡献 | 值得(可验证来源) |
| 公司内部受控仓库 | 可选;强制会带来密钥管理成本 |
| 需要合规审计(金融/医疗) | 建议开,并与 CI 校验联动 |
代价:签名失败会阻断提交(gpg failed to sign the data),团队必须先统一密钥配置方式。
10. 五个反模式
| 反模式 | 后果 | 正确做法 |
|---|---|---|
直接 git push origin test |
绕过 CR,测试环境被污染 | 走 MR;分支保护开启 |
git push -f 到共享分支 |
抹掉他人提交(S01-J01 §5 的对象还在,但引用没了) | 只在个人分支用;共享分支走 revert |
| 一个 MR 混 5 个特性 | 无法评审、无法回滚 | 拆 MR,按"一件事"切 |
Commit 里混 style 与 feat |
git bisect 与回滚都会误伤 |
分开提交 |
分支从 test 拉 |
建在未验证代码上 | 已上线项目从 master/prod 拉 |
11. 旧文对照
| 旧文章节 | 内容 | 本篇处理 |
|---|---|---|
| 一 | 为什么要有规范(fix/update 的灾难) |
保留论证,补"四个可检查产物" |
| 二 | 九类 Commit 类型 | 保留;新增格式之争(斜杠 vs 括号)与生态兼容性判断 |
| 三–十二 | 九类各自的示例(feat/bugfix/hotfix/perf/style/refactor/test/docs/chore) | 压缩为 §2 表 + 好坏对比;示例移入类型表 |
| 十三 | 不推荐的 Commit 写法 | §2.2 判据化 |
| 十四 | 分支规范(test/pre/prod/master) | §3 保留,补与 trunk-based/GitFlow 的取舍 |
| 十五–十六 | 已上线/未上线项目从哪拉 | §3.1 保留并补"为什么不能从 test 拉"的解释 |
| 十七 | 分支命名 | §3.2 保留 + 反例 |
| 十八–二十 | 标准流程、提交、为什么不能直接 push test | §4 保留,补"分支存活 ≤3 天"等纪律 |
| 二十一–二十五 | MR、CR、为什么必须 CR | §5、§6 重组:新增 MR 模板与 CR 顺序 |
| 二十六 | CI 自动工作 | §7 补阶段表与"谁负责" |
| 二十七–二十九 | 提测、Bug、Hotfix 流程 | §8 保留 Hotfix 两条铁律(旧文有但没强调回合 master 的后果) |
| — | 无自动化、无签名、无反模式 | 新增 §2.3(钩子实测)、§9、§10 |
12. 自测
- 已上线项目为什么不能从
test拉新功能分支? feat/user: ...与feat(user): ...除了外观,还有什么实质差别?- Hotfix 只合
prod不合master,什么时候会炸? .git/hooks里的钩子为什么团队共享不了?怎么解决?- CR 时"先看格式再看方案"错在哪?
13. 下一篇
S01-J03 · Git 手术与效率:reset、revert、reflog、bisect、alias
流程规范了,事故仍然会发生:误提交到 main、误 push、误 merge、找不到是哪次引入的 bug。下一篇把五种事故各复现一次,给出"该 reset 还是该 revert"的判断标准,并用 git bisect 把"哪次改动引入问题"从人工排查变成二分查找。
本篇属于系列《S01 · 开发环境与工具链》。旧文来源:A-555(543 点位);扩展点 E-S01-016/017/018。commit-msg 钩子拦截效果为 2026-11-05 实测。