基于分支的完整工作流程示例
-
基于分支的完整工作流程示例
场景1:功能开发分支
分支创建
git checkout master git pull git checkout -b feature/TG-1022-add-tel-to-sale开发完成后签入
git add . git commit -m "feat: add telephone field to sale module" git push origin feature/TG-1022-add-tel-to-saleCI/CD 流程
1. Setup 阶段
- 生成版本号:
2855.2026.104.12345 - 判断分支:
feature/TG-1022-add-tel-to-sale→ENVIRONMENT=development - 下载配置:
https://version.lodatone.com/erp1.0/development/urls.txt
2. Build 阶段
- 使用开发环境的
Directory.Build.props - 构建 NuGet 包:
Loda.Erp.Sales.2855.2026.104.12345.nupkg
3. Push NuGet 阶段
- 推送到 NuGet 仓库
4. Update Version 阶段
- 调用 API:
GET /updateversion/erp1.0/Loda.Erp.Sales/2855.2026.104.12345/development - 更新文件:
wwwroot/erp1.0/development/version-numbers/Loda.Erp.Sales.props wwwroot/erp1.0/development/Directory.Build.props
5. Deploy 阶段(Docker 示例)
deploy-dev(自动执行)
部署到开发环境: stage: deploy-dev extends: .deploy-docker-base environment: name: development variables: DEPLOY_SSH_HOST: "10.0.3.204" HOST_PORT_OFFSET: "0" rules: - if: '$CI_COMMIT_BRANCH =~ /^(develop|next|feature\/|bugfix\/|fix\/)/' when: on_successdeploy-test(手动执行)
部署到测试环境: stage: deploy-test extends: .deploy-docker-base environment: name: test variables: DEPLOY_SSH_HOST: "10.0.3.204" HOST_PORT_OFFSET: "1" when: manual rules: - if: '$CI_COMMIT_BRANCH =~ /^(develop|next|feature\/|bugfix\/|fix\/)/'场景2:生产分支发布
代码合并到 master
git checkout master git pull git merge feature/TG-1022-add-tel-to-sale git push origin masterCI/CD 流程
1. Setup 阶段
- 生成版本号:
2855.2026.104.12346 - 判断分支:
master→ENVIRONMENT=production - 下载配置:
https://version.lodatone.com/erp1.0/production/urls.txt
2. Build 阶段
- 使用生产环境的
Directory.Build.props - 构建 NuGet 包:
Loda.Erp.Sales.2855.2026.104.12346.nupkg
3. Push NuGet 阶段
- 推送到 NuGet 仓库
4. Update Version 阶段
- 调用 API:
GET /updateversion/erp1.0/Loda.Erp.Sales/2855.2026.104.12346/production - 更新文件:
wwwroot/erp1.0/production/version-numbers/Loda.Erp.Sales.props wwwroot/erp1.0/production/Directory.Build.props
5. Deploy 阶段(Docker 示例)
deploy-staging(手动执行)
部署到准正式环境: stage: deploy-staging extends: .deploy-docker-base environment: name: staging variables: DEPLOY_SSH_HOST: "10.0.3.202" HOST_PORT_OFFSET: "2" when: manual rules: - if: '$CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "main"'deploy-production(手动执行)
部署到正式环境-服务器1: stage: deploy-production extends: .deploy-docker-base environment: name: production variables: DEPLOY_SSH_HOST: "10.0.3.201" HOST_PORT_OFFSET: "3" when: manual rules: - if: '$CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "main"' 部署到正式环境-服务器2: stage: deploy-production extends: .deploy-docker-base environment: name: production variables: DEPLOY_SSH_HOST: "10.0.3.202" HOST_PORT_OFFSET: "3" when: manual rules: - if: '$CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "main"'场景3:紧急修复(hotfix)
创建 hotfix 分支
git checkout master git pull git checkout -b hotfix/TG-1025-fix-critical-bugCI/CD 流程
- 环境判断:
hotfix/*→ENVIRONMENT=production - 版本号写入:
wwwroot/erp1.0/production/ - 部署 stage:只有
deploy-staging和deploy-production
完整的 .gitlab-ci.yml 示例
include: - remote: 'https://gitee.com/dotnet-geek/gitlab-ci/raw/master/docker-deploy.yml' - project: 'deploy/env-config' ref: master file: 'docker/products/erp1.0-sales/all.yml' variables: PRODUCT_NAME: "erp1.0" COMPONENT_NAME: "Loda.Erp.Sales" DOCKERFILE_PATH: "src/Loda.Erp.Sales.Web/Dockerfile" DOCKER_IMAGE_REPO: "docker.lodatone.com/erp1.0-sales" CONTAINER_BASENAME: "erp1.0-sales" CONTAINER_PORT: "8080" # stages 会从 docker-deploy.yml 继承 # - setup # - build # - push-nupkg # - update-version # - deploy-dev # - deploy-test # - deploy-staging # - deploy-production版本号文件结构
开发环境
wwwroot/erp1.0/development/ ├── Directory.Build.props # 包含所有组件的版本号 ├── urls.txt # 所有 .props 文件的 URL 列表 └── version-numbers/ ├── Loda.Erp.Sales.props # 2855.2026.104.12345 ├── Loda.Erp.Purchase.props # 2855.2026.103.12300 └── ...生产环境
wwwroot/erp1.0/production/ ├── Directory.Build.props # 包含所有组件的版本号 ├── urls.txt # 所有 .props 文件的 URL 列表 └── version-numbers/ ├── Loda.Erp.Sales.props # 2855.2026.104.12346 ├── Loda.Erp.Purchase.props # 2855.2026.103.12301 └── ...优势总结
- 环境隔离:开发分支不会影响生产环境的版本号
- 并行开发:多个 feature 分支可以同时开发,共享开发环境的版本号
- 灵活部署:
- 开发分支:自动部署到 dev,手动部署到 test
- 生产分支:手动部署到 staging 和 production(安全考虑,需要人工确认)
- 版本追溯:每个环境都有独立的版本号历史
- 安全性:生产环境的部署必须手动触发
注意事项
- 开发环境版本号更新频繁:每次 feature 分支推送都会更新
- 生产环境版本号稳定:只有 master/main/hotfix 分支才会更新
- 版本号独立演进:开发和生产环境的版本号各自独立,无需同步
- 分支命名规范:严格遵守分支命名规范,确保自动判断正确
常见问题
Q1: 为什么 staging 不自动部署?
A: 出于安全考虑:
- staging 是生产环境的镜像,是最后的质量关卡
- 需要团队 leader 或 QA 人工确认后再部署
- 防止未经验证的代码进入类生产环境
如果你的团队有不同的流程,可以修改为自动部署:
部署到准正式环境: when: on_success # 改为自动部署Q2: 开发环境和生产环境的版本号是什么关系?
A: 两个环境的版本号完全独立,各自演进:
工作流程:
- 新功能开发时,必须从 master 分支创建 feature 分支
- feature 分支第一次构建时,会下载开发环境的
Directory.Build.props - 这个开发环境的配置文件,最初应该是从生产环境复制过来的(手动初始化)
- 之后,开发环境和生产环境各自独立更新
初始化开发环境(只需要做一次):
# 在版本号服务器上执行(首次设置时) # 将生产环境的稳定版本复制到开发环境作为起点 xcopy wwwroot\erp1.0\production\*.* wwwroot\erp1.0\development\ /E /I /Y之后的演进:
- 开发环境:每次 feature 分支推送都会更新版本号(快速迭代)
- 生产环境:只有 master 分支推送才会更新版本号(稳定版本)
- 两者不需要再同步,各自独立
Q3: 如果多个 feature 分支同时开发,版本号会冲突吗?
A: 不会冲突,因为:
- 每个分支推送时都会生成新的版本号(基于 pipeline ID)
- 所有 feature 分支共享开发环境的版本号
- 后推送的分支会使用最新的版本号
- 这是正常的并行开发模式
Q4: 开发分支部署,只需要 dev 和 test,不需要 staging 和 production 吗?这是最佳实践吗?
A: 大多数团队的“默认最佳实践”确实是:
- 开发分支(develop/feature/bugfix) 主要面向研发自测与联调,所以一般只需要 dev/test。
- staging/production 更偏向“发布链路”,通常由 master/main/hotfix 触发。
背后的行业共识是:
- 环境职责清晰:dev/test 用于快速迭代;staging 用于发布前的最后验收;production 用于真实用户。
- 风险隔离:feature 分支不应该占用发布资源/发布窗口,也不应该频繁污染 staging。
- 权限与审计:staging/production 通常需要更严格的权限控制、审批和变更记录。
但也有例外(不算“反常”):
- 你希望每次合并到
develop都自动部署到 staging 做持续验收(有专门的 QA 流程)。 - 你们没有 staging,只用 test 充当准正式。
所以它算是业界常见的默认实践,但不是唯一答案;关键是让环境的“职责”稳定、团队共识一致。
Q5: master/main/hotfix 分支,就不需要 dev 和 test 了吗?这是最佳实践吗?
A: 通常是的:
- master/main/hotfix 代表“可发布”的稳定线,部署链路一般是 staging -> production。
- dev/test 主要服务开发过程,master/main/hotfix 不一定需要再走一遍。
不过这里有一个容易混淆的点:
- master/main 的构建产物(镜像/包)仍然建议先部署到 staging 做一次最终验证。
- dev/test 更像“开发过程环境”,staging/production 更像“发布过程环境”。
如果你们想更严格,也可以要求 master/main 在进入 staging 前,必须保证:
- MR/PR 已经在 feature 分支通过 dev/test 验证;
- master/main pipeline 只做“复现式验证”(staging)+ “发布”。
生产 Docker 回滚:如何优雅地通过流水线回滚到上一个版本?
原则(最佳实践)
-
镜像不可变:生产环境部署必须使用不可变标识。
- 推荐使用 镜像 digest(例如
repo@sha256:...),或者使用永不复用的 tag(例如v2026.0104.12345/${CI_PIPELINE_ID})。 - 不推荐用
latest这种会漂移的 tag 作为生产部署依据。
- 推荐使用 镜像 digest(例如
-
回滚=重新部署一个“已知好的镜像”:生产回滚不做“源码回退再重发”,而是直接把服务切回上一个稳定镜像。
-
把“上一次成功部署的镜像标识”记录下来:
- 记录到服务器(如
/data/<app>/last_successful_image.txt) - 或记录到版本号服务/配置仓库
- 或记录到 GitLab Environment/Deployment(如果你们用 GitLab Environments)
- 记录到服务器(如
一种可落地的流水线方案(推荐)
在生产部署 job 成功后:
- 把本次部署使用的
IMAGE(tag 或 digest)写到远端服务器某个文件(例如:current_image.txt) - 同时把上一次的
current_image.txt备份为previous_image.txt
当需要回滚时:
- 触发一个 手动 job:回滚到上一个版本
- 它读取
previous_image.txt,然后用该镜像重新docker compose up -d或docker service update
回滚 job 在流水线里的表现
你可以提供两种回滚方式:
- 回滚到上一个版本:无需输入参数(最常用)
- 回滚到指定版本:手动输入
TARGET_IMAGE(应急时非常好用)
注意事项
- 回滚前最好先做一次健康检查确认“当前版本确实异常”。
- 回滚成功后,建议把“故障版本”标记出来(例如打一个 Git tag 或在变更记录里写清楚)。
- 如果你的部署模板目前没有“记录 current/previous 镜像”能力,那么需要在
docker/deploy.yml中增加这段能力(这属于模板层的增强)。
- 生成版本号:
-
我在WindSurf里,设置了“全局规则”以后,IDE会自动为我准备完美git语句:

-
WindSurf自动感知/使用工作区的规则,为我生成了有Key的注释:

因为我更新(保存)了工作区的规则如下:

-
但是hotfix的代码,最终也是要合并到develop和master?
是的,流程是这样的:
- hotfix 分支代表紧急修复,通常从
master切出(确保基准与线上一致)。 - 完成修复后,先合并回
master(恢复线上),同时需要把同一修复再合并回develop,否则开发分支缺少这次补丁,后续再从develop合回master时容易把修复覆盖掉。
所以“最终 master 和 develop 都要包含 hotfix 的代码”是硬性要求,这样才能保持所有环境一致、避免回归。
- hotfix 分支代表紧急修复,通常从
-
这个如何监管呢?感觉非常容易失控
要防止 hotfix(或任何分支)漏合、乱合,可以用这几层手段来“硬管控”:
-
CI 强校验(GitLab CI / Pipelines)
- 在所有 MR Pipeline 里检查:
- 分支名必须匹配
hotfix/<KEY>-...、feature/<KEY>-...等; - Commit message 第一行含合法 Key;
- MR 的目标分支符合策略(hotfix 指向 master,但合并后有后续任务指向 develop)。
- 分支名必须匹配
- 若校验不通过直接失败,阻止合并。
- 在所有 MR Pipeline 里检查:
-
Protected Branch + Merge Request 流程
- 保护
master/develop分支,只允许通过 MR(且 Pipeline 通过)才能合并; - 禁止直接 push到受保护分支;
- 设定最少一名 Reviewer 审核,确保每次 hotfix 在 master 合并后,会马上安排一个“同步 develop”的 MR(可用模板提醒)。
- 保护
-
自动化同步提醒
- 在仓库里加脚本或机器人(如 GitLab Bot)检测 hotfix MR 合并后,若一定时间内没有对应 develop MR,就自动发提醒/创建 issue;
- 或者把“同步 develop”写进 MR 模板、Checklist,审核人必须勾选才能通过。
-
分支策略文档化 + 周会 Review
- 把流程写进规则(已经有),再结合例行的改动审查(如周会 review 最近 master 合并),一旦发现直接在 master 改动,就定位责任人并要求补齐流程,形成约束氛围。
-
工具辅助
- 使用 GitLab Hooks/服务器端脚本,在 master 合并后自动检测是否存在同一 Key 的 develop MR,如果没有,自动创建任务/issue。
- 若资源允许,可引入 “multi-target merge” 脚本:hotfix MR 合并 master 后自动触发一个脚本,把同一 commit cherry-pick 到 develop 并发 MR。
只要 CI + 受保护分支 + 审核流程严格执行,再辅以提醒/自动化,就能把“确保 master/develop 都包含 hotfix”变成硬性要求,基本不会失控。
-
-
Git 中常见的三种整合策略简单说明如下(都是把功能分支合进目标分支,只是历史呈现不同):
-
Merge(普通合并)
- 保留双方原始提交,再生成一个新的合并提交。
- 优点:历史完整、不改写已有提交;缺点:分支图会出现分叉。
- 场景:多人协作、需要保留完整记录时的默认做法。
-
Rebase(变基)
- 把功能分支的提交“搬到”目标分支最新提交之后,好像一直基于它开发。
- 优点:历史呈线性,很干净;缺点:会改写提交哈希,容易引发冲突,不适合已推送共享的分支。
-
Squash(压缩合并)
- 把功能分支所有提交压成一个提交再合并。
- 优点:目标分支历史简洁;缺点:功能分支内部的细节提交丢失,回溯困难。
- 场景:功能分支提交非常琐碎、只想留一个总结提交时使用。
如果你没有特别需求,建议直接使用 Merge,操作相对安全,也符合大多数团队习惯。
下一步请告诉我要合并的功能分支是哪个(比如feature/LT-94-friendly-timeout-error),我就按 Merge 流程帮你在develop、master分支各合并一次并提交。 -