1. 问题背景:从“人人可推master”到“发布即灾难”
去年Q3,我们团队从6人扩张到25人。起初大家习惯性地直接往master上推代码,GitHub上每天都有十几条“merge branch 'feature-xxx'”的记录。直到有一天,前端同事推了一个未编译的less文件,导致线上首页直接白屏——那次事故持续了3小时。
痛定思痛,我们决定彻底重构Git工作流。核心痛点有三个:
- 分支权限失控:所有成员都能push master,没有review门槛
- 冲突地狱:多人同时改同一个文件,merge时上下文丢失
- CI空转:Jenkins每次push都触发构建,但失败率高达35%
我们的目标很明确:让每次merge都可追溯,让每个提交都经过机器和人双重校验。
2. 环境与版本:我们用的工具链
| 工具 | 版本 | 说明 |
|---|---|---|
| Git | 2.39.1 | 老版本会有git switch兼容问题 |
| GitLab | 15.8.0 | 使用Enterprise版,支持code owner |
| Jenkins | 2.414.2 | 搭配Docker agent实现隔离构建 |
| Node.js | 18.16.0 | 前端项目,使用pnpm 8.x |
服务器是4核8G的阿里云ECS,构建并发控制在3个,避免IO瓶颈。
3. 分支策略设计:改良版Git Flow + 环境映射
我们没有完全照搬Git Flow,因为那个太重了。我们的分支模型:
master (生产) ← 只接受release合并
↑
release/1.x (预发布) ← 只接受hotfix和feature的squash merge
↑
develop (集成) ← 所有feature分支的汇合点
↑
feature/xxx (个人分支) → 必须从develop切出
hotfix/xxx (紧急修复) → 从master切出,修复后同时merge回master和develop
关键规则:
- 禁止直接push develop和master,只能通过MR(Merge Request)
- feature分支命名规范:feature/【需求编号】-【简述】,例如feature/123-login-captcha
- hotfix必须带issue链接,否则CI直接拦截
我们用了.gitlab/merge_request_templates/default.md来强制MR描述格式:
## 需求描述
- 关联issue: #【编号】
- 变更类型: 【feat/fix/docs/refactor】
## 测试计划
- [ ] 单元测试通过
- [ ] 手动验证截图
## 风险点
- 【可选】影响范围说明
4. 核心实现:CI/CD集成与自动化卡点
4.1 .gitlab-ci.yml核心配置
我们的CI分三个阶段:test(单测+lint)、build(打包)、deploy(按分支环境部署)。这里是我最得意的部分——用rules实现分支环境映射:
image: node:18.16.0-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
variables:
# 构建产物缓存,加速二次构建
NPM_CONFIG_CACHE: /cache/npm
stages:
- test
- build
- deploy
# 所有分支都执行test
test:
stage: test
script:
- pnpm install --frozen-lockfile
- pnpm run lint
- pnpm test -- --coverage
artifacts:
paths:
- coverage/
expire_in: 1 week
# 只有develop和release分支才构建生产包
build:
stage: build
rules:
- if: '$CI_COMMIT_BRANCH == "develop" || $CI_COMMIT_BRANCH =~ /^release/'
script:
- pnpm run build:prod
artifacts:
paths:
- dist/
- docker/
# 部署脚本,按分支选择环境
deploy:
stage: deploy
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
variables:
DEPLOY_ENV: "staging"
- if: '$CI_COMMIT_BRANCH =~ /^release/'
variables:
DEPLOY_ENV: "production"
script:
- echo "启动服务,环境: $DEPLOY_ENV"
- docker build -t myapp:${CI_COMMIT_SHORT_SHA} .
- docker push registry.example.com/myapp:${CI_COMMIT_SHORT_SHA}
environment:
name: $DEPLOY_ENV
关键点:
- rules替代了旧的only/except,逻辑更清晰
- 缓存用了CI_COMMIT_REF_SLUG做key,不同分支缓存隔离
- 生产部署只在release分支,避免develop误触发
4.2 pre-commit钩子:把低级错误挡在门外
我们在.pre-commit-config.yaml里配置了三个钩子:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.5.0
hooks:
- id: commitlint
stages: [commit-msg]
additional_dependencies: ['@commitlint/config-conventional']
commitlint规则在commitlint.config.js里:
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'refactor', 'test', 'chore']],
'header-max-length': [2, 'always', 72]
}
};
这保证了commit message符合Conventional Commits规范,后续生成CHANGELOG时能自动分类。
4.3 Code Review流程
我们的MR流程在GitLab里配置了三条硬性规则:
- 必须至少1个Approval才能merge,且不能是自己的approval
- Code Owner机制:
CODEOWNERS文件里指定核心模块归属,比如:
# 前端核心组件
/src/components/ @前端组长
# API层
/src/api/ @后端负责人
- 流水线必须绿色:通过GitLab的
Merge request pipeline设置,MR更新时自动跑CI
5. 踩坑与优化:那些血与泪的教训
坑1:merge方式的选择
第一次用GitLab默认的merge commit,结果develop历史乱成一团,回滚困难。后来改成squash merge,每个feature分支只保留一个commit,历史瞬间清爽。但注意:squash后如果分支还在,再次merge会冲突。
坑2:CI缓存过期
有次改了package.json但没改pnpm-lock.yaml,CI用了旧缓存,导致线上装了错误版本。后来在test阶段加了changes判断:
test:
script:
- if [ -f "pnpm-lock.yaml" ]; then pnpm install --frozen-lockfile; else pnpm install; fi
坑3:Jenkins和GitLab的权限冲突
一开始GitLab的webhook触发Jenkins,但Jenkins用同一个账号推送结果,导致死循环。后来改用GitLab自带的CI(.gitlab-ci.yml),彻底放弃Jenkins——少维护一个系统,香。
优化效果数据(对比改造前3个月和改造后3个月):
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 平均发布周期 | 2天 | 4小时 |
| 线上故障率 | 每月3-4次 | 每月0-1次 |
| MR合并等待时间 | 平均8小时 | 平均1.5小时 |
| 冲突解决时间 | 单次30分钟 | 单次10分钟 |
6. 总结与建议
这套工作流核心不是工具,而是规则强制化。GitLab的权限控制、CI的自动化检查、Code Owner的责任到人,三者缺一不可。
如果你们团队还处于“能跑就行”的阶段,我建议分三步走:
1. 第一周:只加分支保护,禁止直接push master,观察冲突率变化
2. 第二周:引入CI的lint和单测,让机器先筛选一轮
3. 第三周:加Code Owner和MR模板,培养review文化
最后一句忠告:别试图一次搞全所有功能,团队会抗拒。先解决最痛的冲突问题,再逐步加码。
(正文完)
如果你对具体的GitLab配置或者CI细节有疑问,欢迎在评论区讨论。我们团队的CODEOWNERS文件模板和完整.gitlab-ci.yml我已经脱敏后放到了GitHub Gist上,链接见评论区。