**

1. 问题背景:当小团队的“随意合并”撞上20人协作

我们团队早期只有5个人,Git用法极其随意:所有人往master推,冲突了当面吼一声。后来产品线扩张,前端、后端、测试、运维加起来20人,代码库从一个拆成四个。问题集中爆发:

  • 分支污染:master上经常出现未完成的功能代码,导致预发环境部署失败。
  • Code Review形同虚设:MR(Merge Request)平均只有0.7条评论,很多直接self-merge。
  • CI失败率高:单元测试和构建脚本在本地能过,推到远端就挂,失败率37%,大家开始忽略CI红叉。
  • 回滚困难:一次上线出问题,发现master里混了三个功能的提交,无法单独revert。

我们决定不再“先污染后治理”,而是设计一套可强制执行的Git工作流。

2. 环境与版本

  • GitLab CE 16.8(自托管)
  • Git 2.40.1
  • GitLab Runner 16.8,Docker executor
  • SonarQube 10.3(代码质量门禁)
  • 项目语言:Java 17 + Maven 3.9.6,前端Vue 3 + Vite 5
  • 分支保护规则:GitLab push rules + merge request approvals

3. 方案设计:改良Git Flow + 短周期Release分支

我们参考了Git Flow和Trunk-Based,最终选择改良Git Flow,因为团队需要同时维护线上热修复和多个功能并行。

分支类型与生命周期:

分支 命名 来源 合并目标 生命周期
main main - - 永久
develop develop main - 永久
feature feature/JIRA-123-xxx develop develop ≤5天
release release/v1.4.0 develop main + develop 3-7天
hotfix hotfix/v1.3.1 main main + develop ≤1天

关键约束:
- main和develop禁止直接push,必须通过MR。
- MR必须满足:至少1个Approval、CI流水线全绿、SonarQube质量门禁通过、无冲突。
- feature分支超过5天未合并则强制rebase develop,避免大爆炸冲突。

4. 核心实现:配置与代码

4.1 GitLab分支保护与MR审批配置

在GitLab项目 → Settings → Repository → Protected branches:

  • main:Allowed to merge = Maintainers,Allowed to push = No one
  • develop:Allowed to merge = Developers + Maintainers,Allowed to push = No one

Merge request approvals:
- Approvals required:1
- 勾选“Prevent approval by author”
- 勾选“Prevent editing approval rules in merge requests”
- 勾选“Remove all approvals when commits are added”

4.2 .gitlab-ci.yml 核心片段

我们采用分阶段流水线:validate → build → test → sonar → deploy-review。以下为可运行的精简版:

# .gitlab-ci.yml
stages:
  - validate
  - build
  - test
  - sonar
  - deploy

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
  SONAR_HOST_URL: "https://sonar.internal.com"
  SONAR_TOKEN: $SONAR_TOKEN_CI

cache:
  key: "$CI_COMMIT_REF_SLUG"
  paths:
    - .m2/repository/

validate:branch-name:
  stage: validate
  script:
    - |
      if [[ ! "$CI_COMMIT_REF_NAME" =~ ^(feature|release|hotfix)/[A-Z]+-[0-9]+-.*$ ]] && \
         [[ "$CI_COMMIT_REF_NAME" != "develop" ]] && [[ "$CI_COMMIT_REF_NAME" != "main" ]]; then
        echo "分支名不符合规范: $CI_COMMIT_REF_NAME"
        exit 1
      fi
  only:
    - merge_requests
    - branches

build:java:
  stage: build
  image: maven:3.9.6-eclipse-temurin-17
  script:
    - mvn clean compile -DskipTests
  artifacts:
    paths:
      - target/classes/
    expire_in: 1 day

test:unit:
  stage: test
  image: maven:3.9.6-eclipse-temurin-17
  script:
    - mvn test -Dtest=*UnitTest
  coverage: '/Total.*?([0-9]{1,3})%/'
  artifacts:
    when: always
    reports:
      junit:
        - target/surefire-reports/TEST-*.xml

sonar:scan:
  stage: sonar
  image: maven:3.9.6-eclipse-temurin-17
  script:
    - mvn sonar:sonar
      -Dsonar.projectKey=my-team-app
      -Dsonar.host.url=$SONAR_HOST_URL
      -Dsonar.login=$SONAR_TOKEN
      -Dsonar.qualitygate.wait=true
  allow_failure: false
  only:
    - merge_requests
    - develop
    - main

deploy:review:
  stage: deploy
  image: bitnami/kubectl:1.29
  script:
    - kubectl set image deployment/app-review app=registry.internal.com/app:$CI_COMMIT_SHORT_SHA -n review
    - kubectl rollout status deployment/app-review -n review --timeout=120s
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: https://$CI_COMMIT_REF_SLUG.review.internal.com
  only:
    - merge_requests

关键参数说明:
- sonar.qualitygate.wait=true:强制阻塞流水线直到质量门禁返回结果,避免“先合并后修复”。
- coverage正则提取覆盖率,低于80%时SonarQube门禁失败。
- deploy:review只在MR时触发,为每个MR创建独立预览环境。

4.3 MR模板与自动检查

在仓库根目录创建.gitlab/merge_request_templates/Default.md:

## 变更类型
- [ ] Feature
- [ ] Bugfix
- [ ] Hotfix
- [ ] Refactor

## 关联JIRA
JIRA-XXX

## 自测清单
- [ ] 本地单元测试通过
- [ ] 新增代码覆盖率 ≥ 80%
- [ ] 无SonarQube blocker/critical
- [ ] 已更新API文档(如涉及)

## 影响范围


## 回滚方案

同时配置GitLab push rule:Commit message必须匹配^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50},否则拒绝push。

5. 踩坑与优化

坑1:SonarQube质量门禁导致MR长时间挂起。
我们最初设置sonar.qualitygate.wait=true,但Sonar扫描排队时MR会卡住。优化:将Sonar扫描拆到独立stage,并设置timeout: 15m,超时后标记为warning而非失败(但主分支仍强制阻塞)。

坑2:feature分支rebase后CI缓存失效。
因为cache key用了$CI_COMMIT_REF_SLUG,rebase后slug不变但commit变了,Maven依赖缓存命中率下降。改为key: "$CI_PROJECT_ID-$CI_COMMIT_REF_SLUG"并加上policy: pull-push,命中率从62%提升到91%。

坑3:hotfix合并回develop时冲突频繁。
原因是hotfix从main拉出,而develop已经前进了很多。我们强制hotfix合并到main后,立即用git cherry-pick将hotfix commit应用到develop,而不是直接merge,减少冲突面。

坑4:Reviewer只看代码不看CI结果。
在MR描述顶部加了一行自动评论:CI Pipeline: $CI_PIPELINE_URL | Sonar: $SONAR_URL,Reviewer点开就能看到质量报告,Review时长从4.2h降到1.1h。

6. 效果数据

运行三个月后(统计周期:2024-06 ~ 2024-08):

指标 改造前 改造后
MR平均Review时长 4.2小时 1.1小时
CI流水线失败率 37% 8%
生产环境回滚次数 5次/月 1次/月
单元测试覆盖率 54% 82%
分支平均生命周期 11天 3.5天
SonarQube blocker数 23 0

最明显的变化是:以前上线前夜全员加班修CI,现在MR一提交,流水线自动跑,Reviewer只关注业务逻辑,部署预览环境直接给产品经理验收。

7. 总结

Git工作流不是越复杂越好,而是约束要能自动执行。我们这套方案的核心就三件事:

  1. 分支命名和生命周期用CI强制检查,不靠自觉。
  2. Code Review必须绑定CI结果和Sonar门禁,否则Reviewer没有依据。
  3. 每个MR都有独立预览环境,让测试和产品提前介入,减少合并后返工。

如果你团队也在20人左右,建议先从main+develop保护 + MR模板 + 基础CI流水线开始,跑通后再加Sonar和预览环境。别一上来就搞全套,容易把团队逼疯。