2、版本控制基础:Git工作流选择、分支策略设计、代码仓库权限管理、Git Hooks触发流水线

版本控制是CI/CD流水线的地基。说实话,我见过太多团队把流水线搭得花里胡哨,结果分支策略一团糟,最后上线前合并冲突能搞到通宵。这一章,咱们就把地基夯实。

2.1 Git工作流选择:别选最火的,选最合适的

Git工作流不是越多越好。我个人习惯根据团队规模和发布频率来选。这里我列三种最常见的,你直接对号入座。

工作流类型 适用场景 核心特点
Git Flow 大型项目、固定版本发布 分支多、规则严、适合传统SaaS
GitHub Flow 小型团队、持续部署 只有main和feature分支,简单粗暴
GitLab Flow 环境分支+功能分支混合 按环境(dev/staging/prod)拉分支

我的建议:如果你的SaaS系统每天要发版3次以上,别用Git Flow。那玩意儿分支太多,合并一次能把你累死。我曾在项目中硬套Git Flow,结果光合并就花了半天。后来换成GitLab Flow,清爽多了。

核心原则:工作流是为人服务的,不是为流程服务的。选一个团队能坚持执行的,比选一个理论完美的更重要。

2.2 分支策略设计:别让分支变成「分支地狱」

分支策略设计,说白了就是定规矩。谁往哪分支上推代码,什么时候合并,都得说清楚。我见过最惨的一次,一个项目有40多个长期分支,没人知道哪个是活的。

2.2.1 推荐的分支模型

  • main分支:只接受PR合并,禁止直接push。每次合并自动触发生产部署。
  • develop分支:日常开发集成分支,所有feature分支从这里拉。
  • feature分支:从develop拉出,命名规范:feature/xxx-功能描述
  • release分支:从develop拉出,只做bug修复,不做新功能。
  • hotfix分支:从main拉出,紧急修复,修复后合并回main和develop。

避坑指南:我曾经规定feature分支必须从最新的develop拉出,结果团队经常忘记rebase。后来我改成:每次提PR前,必须rebase一次develop。这个习惯救了我们很多次。

2.2.2 分支命名规范

命名不规范,运维两行泪。我建议统一用这个格式:

feature/<项目代号>-<功能简述>
bugfix/<问题编号>-<问题简述>
hotfix/<版本号>-<紧急修复描述>
release/<版本号>

举个例子:feature/PAY-123-新增支付宝支付。这样一眼就能看出是哪个项目、什么功能。

2.3 代码仓库权限管理:别让所有人都能改main

权限管理是很多团队容易忽略的点。你想想看,如果每个开发都能直接push到main,那流水线还有什么意义?

2.3.1 推荐权限模型

角色 权限 说明
Owner 全部权限 通常1-2人,负责仓库设置、删除分支
Maintainer 合并PR、管理分支 核心开发者,可以合并到main
Developer 创建分支、提交PR 普通开发,不能直接合并到main
Reporter 只读权限 QA、产品经理,只能看代码

注意:千万不要给所有人Maintainer权限。我见过一个团队,20个人全是Maintainer,结果有人误删了release分支,回滚花了一整天。权限越少,事故越少。

2.3.2 分支保护规则

在GitLab或GitHub上,一定要设置分支保护。我通常这样配:

  • main分支:必须通过CI检查才能合并,至少1个approve,禁止强制push。
  • develop分支:必须通过CI检查,至少1个approve。
  • release分支:必须通过CI检查,至少2个approve。

2.4 Git Hooks触发流水线:让代码自己「报警」

Git Hooks是流水线的触发器。说白了,就是当有人push代码时,自动告诉CI系统:「嘿,有新代码了,快跑流水线!」

2.4.1 常用Hook类型

Hook名称 触发时机 典型用途
pre-commit 提交前 代码格式化、静态检查
commit-msg 提交信息写入后 检查提交信息格式
pre-push 推送前 运行单元测试、检查分支名
post-receive 推送后(服务端) 触发CI/CD流水线

2.4.2 实战:用pre-push Hook触发流水线

嗯,这里要注意。客户端Hook是本地执行的,服务端Hook才是真正触发流水线的。我建议在服务端配置post-receive Hook。

举个例子,在GitLab上配置Webhook:

# 在GitLab项目设置中 -> Webhooks
URL: https://your-ci-server.com/webhook/gitlab
Secret Token: your-secret-token
Trigger: Push events, Merge request events
SSL verification: Enable

然后在CI服务器上接收这个请求,启动流水线。我习惯用这种方式:

# 伪代码示例
def handle_webhook(request):
    event = request.headers.get('X-Gitlab-Event')
    if event == 'Push Hook':
        branch = request.json['ref'].split('/')[-1]
        if branch in ['main', 'develop', 'release/*']:
            trigger_pipeline(branch)
    elif event == 'Merge Request Hook':
        if request.json['object_attributes']['action'] == 'merge':
            trigger_pipeline('merge-check')

我的经验:不要所有push都触发流水线。我曾在项目中设置「只有push到main、develop、release/*时才触发」,结果feature分支的push从来不跑CI。后来我改成:feature分支push时跑轻量级检查(lint+单元测试),只有合并到develop时才跑完整流水线。这样既节省资源,又不漏检查。

2.4.3 客户端Hook:把问题扼杀在本地

服务端Hook是最后一道防线。但更好的做法是在本地就发现问题。我推荐用husky(前端项目)或pre-commit框架来管理客户端Hook。

# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run lint
npm run test:unit

# 如果lint或测试失败,提交会被阻止

为什么要这么做?因为等代码推到远程再发现格式问题,已经浪费了CI资源。我曾经有个项目,团队20个人,每天CI排队要等10分钟,就是因为太多人把格式错误的代码推上去了。后来加了pre-commit Hook,排队时间直接降到2分钟。

2.5 避坑总结

最后,我把自己踩过的坑总结一下,你直接拿去用:

  • 分支太多?定期清理已合并的分支,设置自动删除策略。
  • 权限太乱?每季度审计一次仓库权限,移除离职人员的权限。
  • Hook不生效?检查Hook文件是否有执行权限,chmod +x .git/hooks/*
  • 流水线触发失败?先检查Webhook的Secret Token是否匹配,再检查CI服务器日志。

版本控制这块,说白了就是「规矩定好,工具配好,剩下的交给自动化」。下一章我们会讲构建与编译,到时候你会发现,如果版本控制没做好,后面全是坑。