Git 团队协作实战:分支模型、提交规范与钩子自动化
# Git 团队协作实战:分支模型、提交规范与钩子自动化
很多团队的 Git 用法是这样的:分支随手开、名字随心起,提交信息写 `update`、`fix`、`修改`,merge 和 rebase 轮着来,历史图查起来像破译密码。命令大家都会,仓库却越来越乱——因为 Git 的问题从来不是"会不会用命令",而是"有没有一套大家都认的约定"。
这篇不讲入门,专讲协作:分支模型怎么选、提交信息怎么写、merge 和 rebase 怎么分工、规范怎么用工具兜底。所有配置片段都可以直接搬进你们团队的仓库和 wiki。
## 一、先对号入座:你们的仓库有这些症状吗
- **分支考古**:仓库里躺着 `dev-backup2`、`test-final-final`,没人敢删,也没人记得它是干嘛的
- **提交失忆**:`git log` 一片 "update",想回滚只能靠文件时间戳猜
- **历史毛线团**:合并方向随意,graph 图交叉纠缠,`bisect` 无从下手
- **规范空转**:Wiki 里的规范没人看,新人靠口口相传,老人不守也没人管
中了两个以上,就值得花一周时间把下面这套约定落地。
## 二、分支模型:先看发布节奏,再谈信仰
三种主流模型没有优劣,只有合不合适——而合不合适,取决于你们的发布节奏和自动化水位。
### Git Flow:为"版本化交付"而生
五类分支:`main`(可发布)、`develop`(集成)、`feature/*`(功能)、`release/*`(发布准备)、`hotfix/*`(线上急救)。
适合 App、SDK、私有化交付这类"攒一个版本、发一次"的节奏。代价是分支多、合并频繁,对持续部署很不友好——如果你的 Web 服务一天发十次版,Git Flow 会把你埋进合并里。
### GitHub Flow:Web 服务的默认起点
只有 `main` + 短生命周期功能分支:开分支 → 提 PR → 评审 + CI → 合入 → 部署。规则少、心智负担低,是大多数团队的合理起点。
### Trunk-Based:自动化成熟后的形态
所有人直接往 `main` 小步提交,分支寿命以小时计,未完成功能用 feature flag 藏起来。它的上限很高,但前提很苛刻:自动化测试覆盖足够、发布流水线足够快、feature flag 基建完善。三项缺一,trunk-based 就是裸奔。
| 维度 | Git Flow | GitHub Flow | Trunk-Based |
| --- | --- | --- | --- |
| 分支数量 | 多(5 类) | 少 | 极少 |
| 上手成本 | 高 | 低 | 中(依赖 flag) |
| 适合场景 | 版本化交付 | Web 持续部署 | 高频发布 |
| 自动化要求 | 中 | 中高 | 高 |
| 典型团队 | App / SDK / 私有化 | 中小 Web 团队 | 平台型团队 |
**选型建议**:从 GitHub Flow 起步,跑顺之后按需演化;做私有化交付的,保留 `release/*` 分支就够,不必全套 Git Flow。别因为"大厂都用 trunk-based"就照搬——大厂的测试基建不是白给的。
## 三、提交规范:Conventional Commits 一招吃饱
格式只有一行:
```
<type>(<scope>): <subject>
```
type 常用就这几个:
| type | 含义 | 对语义化版本的影响 |
| --- | --- | --- |
| `feat` | 新功能 | minor |
| `fix` | 缺陷修复 | patch |
| `perf` | 性能优化 | 通常 patch |
| `refactor` | 重构(不改外部行为) | 无 |
| `style` | 代码格式调整 | 无 |
| `docs` | 文档 | 无 |
| `test` | 测试 | 无 |
| `build` / `ci` | 构建、流水线 | 无 |
| `chore` | 其他杂项 | 无 |
| `revert` | 回滚 | 随被回滚的提交 |
好处立竿见影:`git log` 可检索、CHANGELOG 可自动生成、版本号可自动推断(配合 semantic-release 这类工具)。
### subject 的四条军规
1. **祈使句、现在时**:写"修复",不写"修复了 / 已修复"
2. **一行不超过 50 字**,细节放到空一行之后的正文里写
3. **结尾不加句号**
4. **写清楚影响面**,而不是只写动作
### 对比示例
```
# ✗ 反面教材
update
fix bug
修改了一下
修复登录页面验证码问题以及顺便更新依赖和调整目录结构和修改部分样式
# ✓ 正确示范
fix(login): 修复验证码倒计时结束后未重置的问题
倒计时归零后组件未重置 state,导致下次输入无法触发发送。
影响 v2.3.0 及以上所有版本。
```
### 粒度:一个提交只做一件事
"顺手改的"是提交历史的第一杀手。拆分用交互式暂存:
```bash
git add -p # 逐块确认要暂存的改动
git commit -m "feat(order): 支持导出 Excel"
git add -p
git commit -m "chore(deps): 升级 exceljs 到 4.4.0"
```
## 四、Merge 还是 Rebase:记住一条黄金法则
> **永远不要改写别人可能已经基于它开发的历史。**
这一条想通了,剩下的都是细节:
| 场景 | 推荐 | 原因 |
| --- | --- | --- |
| 整理自己未推送的提交 | `git rebase -i` | 合并、改序、改写随心,反正没人基于它开发 |
| 主干更新拉到自己分支 | rebase 或 merge,团队统一即可 | "统一"比"选哪个"更重要 |
| 功能分支合入 main | squash merge | 一个 PR 一个提交,回滚是原子的 |
| hotfix 合入 main | `merge --no-ff` | 保留合并节点,方便定位线上版本 |
| 已 push 的共享提交出问题 | `git revert` | 前进式修复,不改写历史 |
force push 只允许出现在一种场景:整理**自己个人的**远端功能分支,并且用 `--force-with-lease`——它在远端有别人的新提交时会拒绝覆盖,比裸 `--force` 安全一个量级:
```bash
git push --force-with-lease origin feat/order-export
```
## 五、高频场景急救手册
每个场景给最小命令序列,建议原样贴进团队 wiki。
**1. 刚提交完,发现漏了文件 / 信息写错了**
```bash
git commit --amend # 补进最后一个提交
git commit --amend --no-edit # 保留原提交信息
```
**2. 想撤销最后一个提交,但改动要保留**
```bash
git reset --soft HEAD~1 # 改动回到暂存区
git reset HEAD~1 # 改动回到工作区(默认 mixed)
```
三个模式的区别:
| 模式 | 历史 | 暂存区 | 工作区 |
| --- | --- | --- | --- |
| `--soft` | 回退 | 保留 | 保留 |
| `--mixed`(默认) | 回退 | 清空 | 保留 |
| `--hard` | 回退 | 清空 | 清空 ⚠️ |
**3. 已经 push 的提交要撤销**
```bash
git revert <hash> # 生成一个"反向提交"
git revert -m 1 <merge-hash> # 撤销一次合并;-m 1 表示保留 main 侧
```
**4. 写到一半,要先切分支修线上问题**
```bash
git stash push -m "wip: 订单导出"
git checkout hotfix/payment-timeout
# ……修完回来
git stash pop
```
**5. 只想要别的分支上的某一个提交**
```bash
git cherry-pick -x <hash> # -x 会在提交信息里注明来源,推荐
```
**6. "上周是哪个提交改坏的?"**
```bash
git bisect start
git bisect bad HEAD # 当前是坏的
git bisect good v2.2.0 # 这个版本是好的
# Git 自动二分切换,每一步测试后标记 good / bad,最后直接给出肇事提交
git bisect reset
```
**7. 误把密钥提交进了仓库**
先当密钥已泄露处理——**轮换密钥永远排在清历史之前**,然后:
```bash
git filter-repo --path config/secrets.py --invert-paths
```
`filter-repo` 已取代官方不推荐再用的 `filter-branch`;不装 Python 生态可以用 BFG。清完强推所有分支,并通知所有同事重新克隆。别忘了:任何克隆和 fork 里都还有旧历史,所以密钥轮换没有商量余地。
**8. 想同时开着两个分支干活,互不打扰**
```bash
git worktree add ../repo-hotfix hotfix/urgent-fix
# ../repo-hotfix 是一个独立工作目录,和当前仓库共享同一套对象
```
## 六、规范不靠自觉:用钩子兜底
写在 wiki 里的规范是建议,装进钩子里的规范才是规范。
### Node 项目:husky + commitlint + lint-staged
```bash
npm i -D husky @commitlint/cli @commitlint/config-conventional lint-staged
npx husky init
```
`commitlint.config.mjs`:
```js
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'subject-max-length': [2, 'always', 50],
'type-enum': [2, 'always', [
'feat', 'fix', 'docs', 'style', 'refactor',
'perf', 'test', 'build', 'ci', 'chore', 'revert',
]],
},
};
```
`.husky/commit-msg`:
```sh
npx --no -- commitlint --edit "$1"
```
`.husky/pre-commit`(只检查暂存区,别全量扫):
```sh
npx lint-staged
```
`package.json`:
```json
{
"lint-staged": {
"*.{js,ts}": ["eslint --fix", "prettier --write"]
}
}
```
### 非 Node 项目:30 行 shell 钩子
`.githooks/commit-msg`:
```sh
#!/bin/sh
first_line=$(head -n1 "$1")
pattern='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,50}'
if ! echo "$first_line" | grep -qE "$pattern"; then
echo "✗ 提交信息不符合规范:$first_line"
echo " 格式:<type>(<scope>): <subject>"
echo " 示例:fix(login): 修复验证码倒计时不归零"
exit 1
fi
```
启用共享钩子(钩子目录不随仓库走,用配置指过去):
```bash
git config core.hooksPath .githooks
```
### 别忘了 CI 是最后一道闸
钩子只在本地生效,绕过只需 `--no-verify`。所以服务端 CI 必须再校验一遍提交信息和 lint——本地钩子是省时间的服务,服务端校验才是底线。
## 七、分支保护与评审:最后一道闸
保护分支规则清单(GitHub、GitLab、CodeArts 等主流平台都有等价配置):
- `main` 禁止直接 push,只能通过 PR 合入
- 禁止 force push 和删除分支
- 至少 1–2 个批准,模块负责人必批(配合 CODEOWNERS)
- CI 全绿才允许合并
- 合并方式统一,建议只允许 squash merge
分支命名:
| 前缀 | 用途 | 示例 |
| --- | --- | --- |
| `feat/` | 新功能 | `feat/order-export` |
| `fix/` | 缺陷修复 | `fix/login-countdown` |
| `hotfix/` | 线上紧急修复 | `hotfix/payment-timeout` |
| `release/` | 发布准备 | `release/v2.3.0` |
| `chore/` | 工程与杂项 | `chore/ci-cache` |
`CODEOWNERS`(GitHub 放 `.github/` 目录,GitLab 放仓库根目录):
```
/frontend/ @fe-team
/services/order/ @order-team @zhangsan
*.md @docs-maintainers
```
评审的两条经验值:PR 改动控制在 **200 行以内**体验最好,超过 400 行后评审质量会明显下降——评审人开始无脑点 approve;单次评审控制在 **60 分钟以内**,超了就拆 PR,别硬看。
## 八、落地路线图:一周一步,别贪快
规范落地最常见的失败姿势,是发一封"即日起执行"的全员邮件。建议按这个节奏走:
1. **第 1 周:保护 main。** 开保护规则 + CI 门禁。零心智成本,收益立现。
2. **第 2 周:commitlint 上线。** 先 warning 跑一周,再切 error,给存量习惯一个缓冲期。
3. **第 3 周:统一分支命名与合并方式。** 老分支不强求改名,新分支执行新规。
4. **第 4 周起:收缩分支模型。** 观察发布频率与自动化水位,再决定是否向 trunk-based 演化。
## 速查卡
| 场景 | 命令 |
| --- | --- |
| 补交文件 / 改信息 | `git commit --amend` |
| 撤销未推送提交 | `git reset --soft HEAD~1` |
| 撤销已推送提交 | `git revert <hash>` |
| 暂存现场 | `git stash push -m "说明"` |
| 摘取提交 | `git cherry-pick -x <hash>` |
| 二分找 bug | `git bisect start / bad / good` |
| 多分支并行 | `git worktree add ../dir branch` |
| 安全强推 | `git push --force-with-lease` |
| 好看的 log | `git log --oneline --graph --decorate --all` |
## 写在最后
Git 协作规范的本质,不是给团队上枷锁,而是把所有人的注意力从"别搞坏仓库"里解放出来。约定清晰、工具兜底、CI 把门之后,分支和提交就退化成纯技术细节——团队真正该讨论的是设计,不是怎么合代码。
从保护 main 开始,把这篇文章里的配置片段搬进你们的仓库吧。
- 点赞
- 收藏
- 关注作者
评论(0)