一、问题背景:我们为什么需要一套工作流

先说结论:没有约定的Git用法,比SVN更难管

我们团队30人左右,后端18人、前端8人、测试4人。2022年从SVN迁到Git后,头三个月基本是“放飞自我”状态:

  • 所有人直接往master推代码,谁的本地跑通了就push;
  • 有人用dev分支,有人用develop,还有人用development,三个分支并存;
  • 发布靠“谁记得最后一次改了什么”,上线出问题就git reset --hard
  • 最夸张的一次,两个同事同时改了同一个配置文件,合并后服务起不来,排查了2小时。

数据很直白:迁移后前3个月,平均每周2.3次合并冲突导致的事故,平均回滚耗时45分钟。这不是Git的问题,是我们没有工作流。

后来我们花了大概两周时间,参考Vincent Driessen的Git Flow,裁剪出一套适合我们规模的策略,并把它固化到GitLab和Jenkins里。这篇文章就是把我们最终跑通的配置完整写出来。

二、环境与版本

先把环境交代清楚,避免你照着配发现对不上:

  • GitLab CE 16.8.2(自建,Docker部署)
  • Git 2.43.0
  • Jenkins 2.426.3 LTS + GitLab Plugin 1.7.16 + Pipeline 2.6
  • 构建环境:Docker 24.0.7,Node 20.11.0 / JDK 17.0.9
  • 部署目标:K8s 1.28集群,通过kubectl滚动更新

分支模型最终定为5类:

分支 用途 生命周期 保护级别
main 生产代码,每次提交对应一个tag 永久 最高
develop 集成开发分支 永久
feature/* 单功能开发 临时
release/* 发布准备 临时
hotfix/* 线上紧急修复 临时 最高

命名规范:feature/JIRA-123-user-loginrelease/1.8.0hotfix/1.7.2-payment-timeout。强制带单号,方便追溯。

三、方案设计:分支怎么走

核心规则就几条,但每条都踩过坑:

  1. main只接受来自release和hotfix的合并,禁止直接push,禁止feature直接合入。
  2. develop接受feature合入,每天至少一次CI全量构建。
  3. feature从develop切出,完成后合回develop,生命周期不超过5天,超了就拆。
  4. release从develop切出,只允许bugfix提交,测试通过后同时合入main和develop。
  5. hotfix从main切出,修完同时合入main和develop,并打tag。

发布节奏:每两周一个release,hotfix随时。

这里有个我们改过的点:Git Flow原版要求release合入main后再合回develop,我们额外加了一步——合回develop时必须通过MR,且强制跑一遍集成测试。因为曾经有一次release分支漏合回develop,导致下个版本把已修的bug又带回来了。

四、核心实现:分支保护 + Code Review + CI/CD

4.1 GitLab分支保护配置

在GitLab项目 → Settings → Repository → Protected branches里配置。也可以通过API批量配置,我们用的是API:

# 保护main分支:只有Maintainer能合并,禁止直接push
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/42/protected_branches?name=main&push_access_level=0&merge_access_level=40&allow_force_push=false"

# 保护develop分支:Developer可合并,禁止直接push
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/42/protected_branches?name=develop&push_access_level=0&merge_access_level=30&allow_force_push=false"

push_access_level=0表示没人能直接push,merge_access_level=40是Maintainer,30是Developer。这一步是关键——只要允许直接push,任何工作流都是纸老虎

同时开启Merge Request的强制检查:

  • Settings → Merge requests → Merge checks:勾选“Pipelines must succeed”和“All threads must be resolved”
  • Merge method选“Merge commit”,保留完整历史
  • 勾选“Delete source branch when merge request is accepted”

4.2 Code Review流程

我们的MR规则:

  • 至少1个Approver,核心模块(支付、权限)要求2个;
  • CI必须绿;
  • MR描述必须填:变更内容、影响范围、测试方式、回滚方案;
  • 单次MR代码量超过400行,Reviewer有权打回要求拆分。

GitLab的.gitlab/merge_request_templates/default.md模板:

## 变更内容


## 关联单号
JIRA-XXX

## 影响范围
- [ ] 涉及数据库变更
- [ ] 涉及接口变更
- [ ] 涉及配置变更

## 测试方式


## 回滚方案

这套模板上线后,MR平均Review时长从原来的11小时降到3.5小时,因为Reviewer不用再反复问“这个改动影响啥”。

4.3 Jenkins CI/CD集成

Jenkinsfile放在项目根目录,用Multibranch Pipeline,自动发现feature/release/hotfix分支。核心逻辑:

pipeline {
    agent any
    environment {
        REGISTRY = 'harbor.example.com/app'
        IMAGE_TAG = "${env.BRANCH_NAME.replaceAll('/', '-')}-${env.GIT_COMMIT.take(8)}"
    }
    triggers {
        // develop每天凌晨2点全量构建
        cron(env.BRANCH_NAME == 'develop' ? 'H 2 * * *' : '')
    }
    stages {
        stage('Checkout') {
            steps { checkout scm }
        }
        stage('Build & Unit Test') {
            steps {
                sh 'npm ci --prefer-offline'
                sh 'npm run test:unit -- --coverage'
            }
            post {
                always { junit 'reports/**/*.xml' }
            }
        }
        stage('SonarQube Scan') {
            when { anyOf { branch 'develop'; branch 'main'; branch 'release/*' } }
            steps {
                withSonarQubeEnv('sonar') {
                    sh 'sonar-scanner -Dsonar.projectKey=app -Dsonar.qualitygate.wait=true'
                }
            }
        }
        stage('Docker Build & Push') {
            when { not { branch 'feature/*' } }
            steps {
                sh """
                    docker build -t ${REGISTRY}:${IMAGE_TAG} .
                    docker push ${REGISTRY}:${IMAGE_TAG}
                """
            }
        }
        stage('Deploy to Staging') {
            when { branch 'develop' }
            steps {
                sh "kubectl set image deployment/app app=${REGISTRY}:${IMAGE_TAG} -n staging"
                sh "kubectl rollout status deployment/app -n staging --timeout=180s"
            }
        }
        stage('Deploy to Prod') {
            when { branch 'main' }
            steps {
                // 生产部署需要人工确认
                input message: "确认发布到生产?", ok: "发布"
                sh "kubectl set image deployment/app app=${REGISTRY}:${IMAGE_TAG} -n prod"
                sh "kubectl rollout status deployment/app -n prod --timeout=300s"
            }
        }
    }
    post {
        failure {
            sh 'curl -X POST -H "Content-Type: application/json" -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"构建失败: ${JOB_NAME} ${BUILD_NUMBER}\"}}" https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=$WEBHOOK_KEY'
        }
    }
}

几个关键参数说明:

  • sonar.qualitygate.wait=true:质量门禁不通过直接失败,避免技术债进develop;
  • kubectl rollout status --timeout=180s:部署超时自动判定失败,触发回滚;
  • 生产部署用input做人工卡点,只有main分支才走。

4.4 自动化版本tag

release合入main后,自动打tag。用GitLab CI的job实现(和Jenkins并存,各管一摊):

# .gitlab-ci.yml
tag-release:
  stage: tag
  only:
    - main
  script:
    - VERSION=$(cat version.txt)
    - git config user.email "ci@example.com"
    - git config user.name "CI Bot"
    - git tag -a "v${VERSION}" -m "Release v${VERSION}"
    - git push origin "v${VERSION}"

五、踩坑与优化

坑1:feature分支生命周期太长。 有个feature开了3周,合回develop时冲突文件47个。后来我们定了硬规则:feature超过5天必须拆,CI里加了个job扫描分支存活时间,超时发告警。

坑2:SonarQube门禁太严。 一开始设了“0新增bug”,结果小需求都过不了。改成“新增bug≤2,覆盖率不下降”,落地顺畅多了。

坑3:Jenkins多分支流水线扫描太频繁。 默认1分钟扫一次,GitLab API被打爆。改成Scan Multibranch Pipeline Triggers设为5分钟,并禁用自动触发,只靠GitLab webhook。

坑4:hotfix合回develop被遗忘。 我们加了个每周五的定时任务,检查所有已合并到main但没合回develop的commit,自动发提醒。

坑5:合并冲突处理。 我们要求所有MR合并前必须本地git rebase develop(feature分支),保持线性历史。这条写进了CONTRIBUTING.md,新人不遵守直接在MR里打回。

六、效果数据

跑了大概半年,数据对比:

指标 改造前 改造后
合并冲突事故 每周2.3次 每月0.6次
平均回滚耗时 45分钟 4.5分钟
MR平均Review时长 11小时 3.5小时
发布频率 每月1次 每两周1次,hotfix随时
生产事故数 每季度5次 每季度1次
CI平均构建时长 6分20秒

回滚快主要是因为每次部署都有明确tag,kubectl rollout undo一条命令搞定。事故少主要是因为Code Review + SonarQube把大部分低级问题拦在了合并前。

七、总结

一句话:工作流不是写给别人看的,是写进工具里强制执行才有用

我们这套方案没有多高级,核心就是三件事:分支保护堵死直接push、MR模板和Review规则让协作有章法、CI/CD把质量门禁和部署自动化。任何一条只停留在文档上,都撑不过一个月。

如果你团队规模在10-50人、正在被Git搞得很痛苦,建议先从“保护main和develop”开始,跑两周再加CI门禁,循序渐进。全套配置我都放在上面的代码块里了,可以直接抄。

最后提醒一句:工具是次要的,团队对规则的认同才是关键。我们当时开了两次全员会专门讲这套流程,新人有onboarding文档,这才真正跑起来。