1. 背景:为什么我们要重写Git工作流

2024年初,团队从10人膨胀到50人,项目从单个微服务裂变为8个独立服务。最初“所有人直接推master + 手动部署”的模式彻底崩溃:
- 一周内平均发生6次合并冲突,修复耗时超过2小时。
- 一次紧急hotfix因分支混乱,导致线上回滚了3次。
- Code Review形同虚设:PR平均挂3天无人review,合并后才发现代码质量参差不齐。

我们意识到:不是工具不行,是流程没设计好。于是我们基于Git 2.35.1环境,设计了一套以“feature分支开发 + develop分支集成 + master分支发布”为核心的工作流,并配套了Code Review和CI/CD自动化。

2. 环境与版本:我们用到的具体工具

  • 操作系统:Ubuntu 20.04 LTS(开发机)、macOS Ventura(部分前端同学)
  • Git版本:2.35.1(2022年发布,支持git switchgit restore
  • 代码托管:GitLab EE 15.10(自建)
  • CI/CD:GitLab CI Runner(版本16.0)+ Jenkins 2.387(用于老旧服务)
  • 代码质量:ESLint 8.56.0 + Prettier 3.1.0 + Husky 9.0.6
  • 自动部署:Docker 24.0.5 + Kubernetes 1.28(开发环境用Minikube)

3. 方案设计:分支策略与Code Review流程

3.1 分支策略:三主干 + 短期特性分支

我们采用了“Git Flow”的简化变体,放弃繁琐的release分支,保留核心分支:

  • master:生产就绪分支,只允许merge request(MR)合并,合并后自动触发CI/CD部署到生产环境。
  • develop:集成分支,所有feature分支从此拉出,合并时自动触发CI/CD部署到测试环境。
  • feature/xxx:从develop拉出,命名规则:feature/-,例如feature/1234-add-user-profile
  • hotfix/xxx:从master拉出,修复后合并回masterdevelop

关键规则
- 不允许直接push到masterdevelop
- 每个feature分支的生命周期不超过5个工作日;
- 合并前必须通过CI/CD流水线和至少1个Code Review批准。

3.2 Code Review流程:从“无门槛”到“硬性门槛”

我们设定了以下强制步骤:

  1. 本地预检查:通过Husky + lint-staged在pre-commit阶段自动运行ESLint和Prettier,不符合规范直接拒绝提交。配置如下(.husky/pre-commit):
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

配套的lint-staged.config.js

module.exports = {
  '*.{js,ts,jsx,tsx}': ['eslint --fix', 'prettier --write'],
  '*.json': ['prettier --write'],
  '*.md': ['markdownlint'],
};
  1. 提交后CI触发:GitLab CI自动运行单元测试、集成测试、代码扫描(SonarQube)。任何阶段失败,MR会被自动锁定,禁止合并。

  2. 人工Review:要求至少2个Approval,且Reviewer必须在24小时内响应。我们设置了一个“LGTM”按钮,但仅当所有自动化检查通过后,Reviewer才能点击。

4. 核心实现:CI/CD集成配置(真实可运行)

4.1 GitLab CI流水线配置

我们的.gitlab-ci.yml片段,支持多环境部署和分支策略:

stages:
  - test
  - build
  - deploy

variables:
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  K8S_NAMESPACE: "development"

cache:
  paths:
    - node_modules/

before_script:
  - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

unit-test:
  stage: test
  image: node:18-alpine
  script:
    - npm ci
    - npm run test:coverage
  only:
    - develop
    - /^feature\/.*$/
    - /^hotfix\/.*$/

build-image:
  stage: build
  script:
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE
  only:
    - develop
    - master

deploy-dev:
  stage: deploy
  image: bitnami/kubectl:1.28
  script:
    - kubectl set image deployment/my-app my-app=$DOCKER_IMAGE -n $K8S_NAMESPACE
  only:
    - develop
  environment:
    name: development
    url: https://dev.example.com

deploy-prod:
  stage: deploy
  image: bitnami/kubectl:1.28
  script:
    - kubectl set image deployment/my-app my-app=$DOCKER_IMAGE -n production
  only:
    - master
  when: manual
  environment:
    name: production
    url: https://example.com

说明
- unit-test阶段只对developfeature/*hotfix/*分支运行,避免浪费master分支的资源。
- deploy-prod设置为手动触发(when: manual),防止误操作直接上线。

4.2 基于Jenkins的旧服务CI配置(兼容性方案)

对于遗留的Java服务(Spring Boot 2.7),我们保留Jenkins流水线。关键配置(Jenkinsfile):

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                checkout scm
                script {
                    env.BRANCH_NAME = scm.getBranches().first().name
                }
            }
        }
        stage('Build & Test') {
            steps {
                sh 'mvn clean package -DskipTests=false'
                junit '**/target/surefire-reports/*.xml'
            }
        }
        stage('Docker Build') {
            steps {
                sh 'docker build -t my-java-app:${BRANCH_NAME}-${BUILD_NUMBER} .'
            }
        }
        stage('Deploy to Dev') {
            when { branch 'develop' }
            steps {
                sh 'kubectl set image deployment/java-app java-app=my-java-app:${BRANCH_NAME}-${BUILD_NUMBER} -n dev'
            }
        }
    }
    post {
        failure {
            emailext (
                subject: "Build Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: "Build failed for branch ${env.BRANCH_NAME}. Check logs: ${env.BUILD_URL}",
                to: 'team@example.com'
            )
        }
    }
}

5. 踩坑与优化:我们犯过的错误和修复

5.1 分支命名混乱导致CI混乱

问题:早期有人将feature分支命名为fix-bug,导致CI的only规则匹配不到,测试和部署全跳过。
优化:在GitLab上设置分支命名规则(Settings → Repository → Protected branches),强制要求feature/hotfix/前缀。同时,在.gitlab-ci.yml中使用正则:/^feature\/.*$/

5.2 Code Review形同虚设:Reviewer只看格式

问题:Reviewer只看Lint通过就点Approval,没有深入审查逻辑。
优化:我们引入“Review Checklist”模板,在MR描述中自动生成(GitLab MR模板)。例如:

### 检查清单
- [ ] 代码是否添加了单元测试?
- [ ] 是否有未处理的异常?
- [ ] 数据库迁移是否向后兼容?
- [ ] 日志是否包含足够上下文?

同时,设置MR必须包含至少1个非格式性评论(代码逻辑相关),否则不能合并。这虽然有点“暴力”,但确实提高了Review质量。

5.3 CI/CD耗时过长:从15分钟降到3分钟

问题:初始CI在每个分支上都跑全量测试(包括E2E),耗时15分钟。
优化
- 使用GitLab CI的needs关键字,让单元测试和构建并行执行(needs: [])。
- 将E2E测试移至develop分支的单独阶段,且只在MR合并后触发。
- 缓存node_modules和Maven的.m2目录,减少依赖安装时间。优化后,feature分支的CI平均耗时3分钟。

6. 效果数据:一个季度后的成效

  • 合并冲突:从每周6次降至每月1-2次,下降78%。
  • PR合并周期:从平均3天降至4小时(包括Review和CI时间)。
  • 线上故障:因代码质量问题导致的回滚从每月3次降为0。
  • CI通过率:从75%提升至95%,因为本地pre-commit提前拦截了格式和语法错误。
  • 开发效率:团队成员反馈“不再害怕合并代码”,新功能上线速度提升约40%。

7. 总结与建议

这套工作流的核心不是技术多炫酷,而是强制自动化 + 温和的规则约束。如果你团队也面临类似问题,我的建议是:

  1. 不要照搬Git Flow,根据团队规模裁剪(比如我们砍掉了release分支)。
  2. CI/CD要尽早集成,哪怕只有单元测试和lint,也能挡掉80%的低级错误。
  3. Code Review要有制度,但别变成形式主义——模板 + 自动化检查 + 有限的人工审核才是平衡点。
  4. 记录数据:冲突率、CI通过率、合并时间,用数字说服团队调整流程。

最后推荐一本书:《Git in Practice》和《持续交付2.0》。工具是死的,流程是活的,关键是大家愿意遵守。


作者注:文中配置均来自我们团队的真实仓库(已脱敏)。如果你对某个环节有疑问,欢迎评论区交流。