1. 问题背景:我们曾经在master上“裸奔”
2023年初,我们团队5个人,项目是Spring Boot微服务,代码直接推master。听起来很爽,对吧?直到某天:
- 同事A改了
UserService.java,同事B同时改了同一文件的不同方法,Git自动合并成功,但逻辑互相覆盖——线上订单数据错乱,紧急回滚。 - 产品经理说“这个功能下周上线”,但代码已经在master里,没法单独剔除——只能连夜手撕
git revert,然后祈祷冲突别爆炸。 - 每次发布,大家围在电脑前,手动合并feature分支到release分支,平均耗时2小时,还经常漏掉某个commit。
我们不是不懂Git,而是没有流程。痛定思痛,我们花了2周时间,重构了整个Git工作流。现在,我把完整方案写出来,含真实配置,希望能帮你少踩坑。
2. 环境与版本:我们用的具体工具链
- Git: 2.39.1 (支持
git switch、git restore,老版本请升级) - GitLab: 15.11.0 (社区版,支持受保护分支、Merge Request审批)
- Jenkins: 2.414.2 (LTS版本,使用Pipeline插件)
- SonarQube: 9.9.0 (社区版,用于静态代码检查)
- 构建工具: Maven 3.8.8 + JDK 17
3. 方案设计:Git Flow变体 + 三层保护
我们不搞纯Git Flow,太繁琐;也不搞GitHub Flow,太简单。采用Git Flow变体:
main (生产环境,受保护,禁止直接push)
├── release/ (预发布分支,从main拉出,测试通过后合回main并打tag)
├── develop (集成分支,受保护,所有feature合入这里)
│ └── feature/xxx (新功能,从develop拉出)
│ └── bugfix/xxx (缺陷修复,从develop拉出)
└── hotfix/xxx (紧急生产修复,从main拉出,修复后直接合入main和develop)
关键规则:
1. 受保护分支:main和develop禁止直接push,必须通过Merge Request (MR) 合入。
2. MR审批:至少1个非作者审批人,且审批人必须是了解该模块的人。
3. CI强制检查:MR未通过Jenkins流水线,无法合并。
4. 核心实现:受保护分支 + Jenkins流水线配置
4.1 GitLab受保护分支配置
在GitLab项目设置中,找到“Repository > Protected Branches”,配置如下:
分支: main
允许合并: Maintainers
允许push: 无 (禁止直接push)
允许强制push: 否
分支: develop
允许合并: Developers + Maintainers
允许push: 无
允许强制push: 否
同时,在“Merge Request > Approval rules”里,设置:
审批人数: 1
审批人类型: 非作者 (Code Owner优先)
4.2 Jenkins Pipeline完整配置 (Jenkinsfile)
我们在仓库根目录放Jenkinsfile,实现MR触发流水线:编译 → 单元测试 → 静态扫描 → 构建镜像。
pipeline {
agent any
tools {
maven 'Maven-3.8.8' // 在Jenkins全局工具里配置的maven
jdk 'JDK-17'
}
environment {
SONAR_HOST_URL = 'http://sonarqube.example.com:9000'
SONAR_TOKEN = credentials('sonar-token') // Jenkins凭据
REGISTRY = 'registry.example.com'
IMAGE_NAME = "${REGISTRY}/myapp:${env.BRANCH_NAME}-${env.BUILD_NUMBER}"
}
stages {
stage('Checkout') {
steps {
// 关键:检出MR源分支,而非目标分支
checkout scm
}
}
stage('Compile') {
steps {
sh 'mvn clean compile -DskipTests'
}
}
stage('Unit Test') {
steps {
sh 'mvn test'
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
stage('SonarQube Analysis') {
steps {
// 注意:需在SonarQube配置GitLab的MR插件
sh """
mvn sonar:sonar \
-Dsonar.projectKey=myapp \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.token=${SONAR_TOKEN} \
-Dsonar.gitlab.commit_sha=${env.CI_COMMIT_SHA} \
-Dsonar.gitlab.ref_name=${env.CI_COMMIT_REF_NAME} \
-Dsonar.gitlab.merge_request_iid=${env.CI_MERGE_REQUEST_IID}
"""
}
}
stage('Build Image') {
when { branch 'develop' } // 仅develop分支构建镜像
steps {
sh """
docker build -t ${IMAGE_NAME} .
docker push ${IMAGE_NAME}
"""
}
}
}
post {
failure {
// 通知团队
emailext (
subject: "构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: "请检查Jenkins构建日志: ${env.BUILD_URL}",
to: 'dev-team@example.com'
)
}
}
}
4.3 MR的CI触发配置
在GitLab项目 .gitlab-ci.yml 中,我们只做一个动作:触发Jenkins。
stages:
- ci
ci:
stage: ci
script:
- curl -X POST http://jenkins.example.com:8080/job/myapp-mr-pipeline/buildWithParameters?token=mysecrettoken
only:
- merge_requests
这样,每次创建/更新MR,GitLab自动调Jenkins。Jenkins里把Build when a change is pushed to GitLab勾选,并设置MR源分支作为构建分支。
5. 踩坑与优化:3个真实案例
坑1:SonarQube扫描在MR里不显示门禁状态
我们配置了SonarQube,但MR里看不到“质量门禁通过/失败”的显示。排查半天,发现是sonar-gitlab-plugin版本不对。SonarQube 9.9需要的是sonar-gitlab-plugin-4.2.0,且必须使用sonar.gitlab.commit_sha和sonar.gitlab.merge_request_iid这两个参数,缺一不可。同时,GitLab的Admin里要设置Allow local requests,否则SonarQube无法回调GitLab API。
坑2:Jenkinsfile里checkout scm导致MR不触发
一开始,我们在Jenkinsfile里写git checkout origin/${env.MR_SOURCE_BRANCH_NAME},结果发现MR合并时构建的是目标分支代码,而不是源分支。正确做法是使用checkout scm,它会根据GitLab插件的上下文自动检出正确的源分支。不要手写git命令。
坑3:develop分支构建镜像,但release分支忘了构建
我们只在develop分支构建开发环境镜像,但release分支也需要构建预发布镜像。后来加了when { branch 'release/*' },并给IMAGE_NAME加了-release后缀,避免与dev镜像混淆。同时,release分支的构建要加--no-cache,因为之前用缓存导致依赖没更新。
6. 效果数据:用数字说话
改造前(2023年1月-2月):
- 平均每周代码冲突:15次
- 发布平均耗时:2小时15分钟
- 线上事故(因合并导致):2次
改造后(2023年3月-4月):
- 平均每周代码冲突:1.5次(下降90%)
- 发布平均耗时:8分钟(Jenkins自动构建+推送,手动点击发布按钮)
- 线上事故:0次
- MR平均从创建到合并:4.5小时(以前是1.5天)
7. 总结与思考
这套Git工作流不是什么银弹,但它让我们从“手工操作”变成“流程驱动”。几点心得:
- 受保护分支不是限制,是保护。刚开始团队成员抱怨“不能直接push很不爽”,两周后没人再提,因为再也不用处理莫名其妙的冲突了。
- CI/CD不是可选项,是必需品。没有自动流水线,Code Review就是走形式,因为合并前根本不知道代码能不能跑。
- 规则要硬编码,不要靠自觉。GitLab的“禁止push”+“强制审批”+“CI检查”三个开关全开,哪怕有人想绕过也没办法。
最后,留个问题:你的团队还在master上裸奔吗? 如果是,建议把这篇文章打印出来贴在工位上。别问我为什么知道——我们都是这么过来的。
附录:关键配置速查
| 工具 | 版本 | 关键配置 |
|---|---|---|
| Git | 2.39.1 | git config --global merge.ff false (保留合并记录) |
| GitLab | 15.11.0 | 受保护分支 + MR审批1人 |
| Jenkins | 2.414.2 | Pipeline + GitLab插件 + 凭据管理 |
| SonarQube | 9.9.0 | 质量门禁: 覆盖率≥60%, 漏洞数=0, 重复率≤3% |