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. 必须至少1个Approval才能merge,且不能是自己的approval
  2. Code Owner机制:CODEOWNERS文件里指定核心模块归属,比如:
# 前端核心组件
/src/components/ @前端组长
# API层
/src/api/ @后端负责人
  1. 流水线必须绿色:通过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上,链接见评论区。