GitLab Merge Requests
文档用途
介绍 GitLab Merge Request 工作流.
术语约定
INFO
虽然 Merge Request 有中文翻译, 但还未被广泛接受.
因此在这篇文档中会选择使用英文术语以及简称.
| 中文 | 英文全称 | 英文简称 | 备注 |
|---|---|---|---|
| 合并请求 | merge request | MR | |
| 源分支 | source branch | MR 就是将源分支合并至目标分支 | |
| 目标分支 | target branch | 通常是主分支 (例如 master 或 main) | |
| 代码审查 | code review | 详见 Merge request approvals | |
| 基于主干的开发 | trunk based dev | TBD | |
| 受保护分支 | protected branch |
MR 的适用场景
Git 开发流程
基于 Git 的开发工作流 (ChatGPT)
向 ChatGPT 提问: 有哪些基于 git 的开发工作流?
例如 git flow, github flow, trunked based 等. 简单介绍一下他们.
以下是 chat gpt 的回答:
Git Flow
Git Flow 是由Vincent Driessen提出的一种流行的工作流程,适用于中大型团队和长期项目。它将开发过程划分为主要的分支和支持分支,包括:
- master分支:用于稳定的、发布-ready 的代码,只包含经过验证的功能。
- develop分支:用于整合不同功能的开发,是从各个feature分支合并而来。
- feature分支:每个新功能都从develop分支创建,完成后再合并回develop分支。
- release分支:准备发布时,从develop分支创建,用于做最后的测试和修复。
- hotfix分支:用于修复master分支上的严重bug,在发布后需要快速修复。
GitHub Flow
GitHub Flow 是一种轻量级的工作流程,适用于小团队和快速迭代的项目。它更加简化,只包含主要的两个分支:
- main分支(或master分支):与Git Flow 中的master分支类似,用于稳定的、可发布的代码。
- feature分支(或topic分支):每个新功能或修复都从main分支上创建,完成后合并回main分支。没有单独的develop分支。
- GitHub Flow强调频繁的、小步快走的迭代开发,每个功能都是通过Pull Request进行讨论、审查和集成的。
Trunk Based Development
Trunk Based Development 是一种持续集成和持续交付(CI/CD)导向的工作流程。
与前两种工作流程不同,Trunk Based Development鼓励开发者将变更尽早地合并到主分支(通常是main或master),避免长期分支的使用。
特点包括:
- 快速的代码合并,减少分支的复杂性。
- 鼓励小型、频繁的提交,以便及早发现问题。
- 强调自动化测试和CI/CD流程的重要性。
这些只是其中一些基于Git的工作流程示例,实际上还有许多其他变体和自定义的工作流程,根据团队的需求和项目的特性来选择适合的工作流程是很重要的。
开源项目中 GitHub Flow 和 TBD (Trunk Based Development) 经常被结合起来使用.
即使用 issue 和 pull request 来讨论, 审查和集成. 使用 CI/CD 来及时发现问题, 频繁迭代版本.
INFO
"Git Flow is for monolith applications developed by a few teams."
大型项目的工作流也逐渐舍弃 Git Flow.
转而将项目拆分成多个代码仓库, 在每个代码仓库依然使用 GitHub Flow 或 TBD.
另外再使用其他工具统筹多个代码仓库的开发, 例如 Notion, Trello.
MR 的优势
简洁, 安全, 可追踪.
冲突不在主分支上解决, 主分支始终是一条直线.
在源分支上合并主分支的最新的 commit 后, 才会合并至主分支 (
fast-forward).
另外也可以通过rebase的方式合并至主分支. 无论哪种方式, 结果都是一样的.
git fast-forward 介绍 (ChatGPT)
"fast-forward" 是指在将一个分支合并到另一个分支时,Git 可以直接移动目标分支的指针,而无需创建一个新的合并提交。
具体来说,当你尝试将一个分支(通常是一个特性分支)合并到目标分支时,如果目标分支的提交历史没有发生变化,也就是说,目标分支没有新的提交,那么 Git 就会执行一个快进式合并。
这种情况下,Git 可以简单地将目标分支的指针(也就是它的 HEAD)直接移动到要合并的分支的最新提交上。这样做的结果是,目标分支的提交历史变得线性,没有额外的合并提交。
快进式合并是一种简洁、清晰的合并方式,它保持了提交历史的线性性,但有时可能会丧失一些分支的详细信息,因为合并后看不到分支的来源。
反之,如果在合并时目标分支有新的提交,就无法进行快进合并,此时 Git 会自动创建一个新的合并提交,将两个分支的更改整合在一起。
总的来说,快进合并是一种合并策略,它利用了分支的线性性,让合并过程变得更加简单和直观。
git rebase 介绍 (ChatGPT)
git rebase 是 Git 中的一个重要操作,用于将一个分支上的提交应用到另一个分支上。它的主要目的是重新排列或整理提交历史,使得项目的提交历史更加清晰、线性,同时还可以解决合并冲突。
下面是关于 git rebase 的一些关键信息:
工作原理:
git rebase将当前分支上的提交逐个地应用到目标分支的顶部。这个过程看起来就像是将当前分支的更改放在目标分支的最新提交之后。它会逐个地应用提交,如果遇到冲突,需要手动解决冲突。整理提交历史:
git rebase可以用来整理提交历史,使得提交历史变得更加线性和清晰,没有分叉和合并的记录。这可以帮助项目保持一个干净的提交历史。用法示例:假设你有一个特性分支
feature,你想要将它的更改应用到主分支main上。你可以使用以下命令进行git rebase:bashgit switch feature git rebase main这将会将
feature分支的提交一个一个地应用到main分支上,从而在main分支上创建一个更线性的提交历史。注意事项:
git rebase修改了提交历史,因此在多人协作的项目中使用时需要小心,避免在公共分支上执行 rebase 操作。- 如果你已经推送了分支到远程仓库,并且其他人也在使用这个分支,尤其是在这种情况下,谨慎使用
git rebase,以免破坏其他人的工作流。
总之,git rebase 是一个用于整理提交历史、保持提交线性性和解决合并冲突的有用工具,但需要谨慎使用,特别是在多人协作的项目中。
每个 issue 都在完成后才会被合并进主分支.
源分支的动作, 不会影响主分支 (合并时可以
squash), 因此可以频繁修改和推送.
解决某个 issue 时不会受到干扰, 因为始终在源分支上做开发, 直至解决这个 issue.
git squash 介绍 (ChatGPT)
git squash 是 Git 中的一个常用操作,用于将多个连续的提交整合成一个单独的提交,从而保持提交历史的整洁和简洁。
具体来说,git squash 将多个提交压缩成一个提交,新的提交会包含原来这些提交的所有更改,但只显示为一个单独的提交。这对于合并一系列相关的小提交成一个更为有意义的单元非常有用,特别是在准备将更改合并到主分支之前。
以下是使用 git squash 的一般步骤:
进入交互式 rebase 模式:
bashgit rebase -i HEAD~n其中
n是你想要合并的最近n个提交的数量。选择要 squash 的提交:
在交互式 rebase 模式中,你会看到一个编辑器窗口,列出了你选定的提交列表,每个提交前面都有一个操作(如
pick、reword、squash等)。将你想要 squash 的提交的操作从
pick改为squash或者s。编辑提交信息:
如果你选择了
squash,Git 会将你选定的提交与前一个提交合并,并打开一个编辑器供你编辑新的合并提交的提交信息。保存并退出编辑器:
保存并关闭编辑器后,Git 会应用你的更改。
完成 rebase:
如果一切顺利,Git 会告诉你 rebase 完成。
请注意,git squash 会修改提交历史,因此在多人协作的项目中,使用时需要谨慎,避免在公共分支上执行这个操作,以免影响其他人的工作流。
总的来说,git squash 是一个有用的工具,可以帮助你保持提交历史的整洁和清晰,特别是在准备将更改合并到主分支之前。
合并进主分支前可以提前检查.
在初步实现某个需求后, 有审核流程来确认是否完整实现, 可继续改进再合并至主分支.
CI/CD 需要跑通, 确保了自动化测试通过, 并能正常编译出包.
可以执行代码审查流程, 代码质量更有保障.
与 Issues 结合紧密.
可以通过 issue 页面中的按钮直接创建 MR, 并且源分支的名称就是 issue 编号.
能够在 MR 的描述中使用 Closing Pattern, 例如 Close #668, Related to #530.
工作流
工作流概述
TIP
并不需要完全按照以下流程, 可根据具体情况省略一些步骤.
只要远程仓库的源分支上有 commit, 就可以开始检测是否满足合并条件.
通过 issue 创建 merge request 流程 (推荐):
通过 本地分支 创建 merge request 的流程:
简单示意图
例如为编号为 #66 的 issue 创建 MR, 源分支名为 issue-66.
详细示意图
如果有多个 merge request 同时进行, 示意图如下.
编号为 #48 和 #66 的 issue 从同一个 commit 开始拉出新分支.
其中 #48 分支更早完成, 对应的分支 issue-48 通过 MR 合并进了主分支.
因此负责 #66 的开发者需要先合并远程仓库中最新的 commit,
然后再推送到 issue-66 分支, 尝试通过 MR 合并进主分支.
具体操作
通过 Issue 新建
通过 issue 创建 MR, 再将对应的源分支拉取至本地.
在 issue 页面的右上角, 点击 Create merge request 按钮.

MR 创建完成后, 需要拉取源分支:
因为在创建 MR 的同时, 在远程仓库创建了源分支, 此时需要将源分支拉取至本地.
随着 git 版本更新, 已经可以使用一行命令, 来拉取本地不存在的远程分支并切换分支.
git switch [branch-name]通过本地分支新建
创建本地分支然后再推送至远程仓库, 以此作为源分支创建 MR.
# 创建新的本地分支, 并切换至新创建的分支
git switch -c [branch-name]
# 将新创建的本地分支推送至远程仓库
git push -u origin [branch-name]通过快捷键 g m 进入当前代码仓库的 Merge Request 浏览页面.
点击页面中的 New merge request 按钮, 开始创建 MR.
TIP
GitLab 检测到 非主分支 有新的 commit, 就会询问是否由此创建 MR.
这个机制还是比较方便的, 尤其是在远程有很多分支的情况下. 推荐使用.

MR 的可编辑项
Title 非常重要, 通常会出现在 MR 合并成功后的 commit message 中.
若通过 issue 创建 MR, Title 会自动设置为 Resolve "某个 issue", 方便直接使用或稍作修改.
首次创建 MR 是建议勾选 Mark as draft, 等之后完成开发并推送至源分支后再取消勾选.

MR description 与 issue description 类似.
都可以使用 GitLab Flavored Markdown 以及通过 / 触发的 quick actions.
可以在 MR description 中使用 Closing Pattern. MR 合并时, 相关的 issue 会自动关闭或提及.
在代码仓库的 .gitlab/merge_request_templates/ 路径下可以使用 .md 创建 template.

Assignee, Milestone, Label 都和 issue 中的概念相同,
如果 MR 是通过 issue 创建的, 还能继承来自 issue 的 Milestone 和 Label.Reviewer 用于指定由谁来做代码审查 (若未使用代码审查流程, 忽略即可).
勾选 Delete source branch when merge request is accepted, 会在 MR 合并后删除源分支.
通常我们会为每个 MR 单独创建源分支, 合并完成后源分支就没用了, 可以从远程仓库中删掉.
勾选 Squash commits when merge request is accepted,
会在合并 MR 时将源分支的多个 commit 合并成一个, 然后再合并进主分支.Squash 能让主分支保持简洁, 且让我们在开发过程中可以无顾虑地频繁推送至源分支.

满足合并条件
同时满足以下条件即可合并:
- 源分支中最新的 commit 通过了 CI/CD 管线.
- 如果团队启用了代码审查流程, 则需要通过代码审查者的审批.
- 当前的 Merge Request 没有标记为 Draft.
- 源分支合并至主分支时没有冲突.
合并 MR 的必要条件之一是 CI/CD 管线通过, 但 CI/CD 执行时间可能很长.
此时可以使用 auto-merge, 点击此按钮后, 会在 CI/CD 通过后自动完成 MR 的合并.
如果处于 auto-merge 状态的 MR 的 CI/CD 管线未通过, 则需要手动处理.
下图是点击了
auto-merge按钮后, 正在等待 CI/CD 通过的 MR.

与 auto-merge 类似的功能也被称之为 Merge Train.
Merge Train 的介绍 (ChatGPT)
Merge train 是一种持续集成和持续部署 (CI/CD) 流程中的一种自动化合并方法。它通常用于团队协作中,特别是在使用版本控制系统(如 Git)进行代码管理时。
在一个典型的 Merge Train 中,当多个开发人员在各自的分支上工作并准备将更改合并到主分支(如 main 或 master)时,他们会将他们的分支推送到共享的代码库。然后,一个自动化的系统会按照一定的顺序逐个将这些分支合并到主分支上,形成一个“列车”式的合并流程。
Merge Train 通常包括以下步骤:
提交到特性分支: 每个开发人员在自己的特性分支上工作,将更改提交到这个分支。
推送到远程仓库: 当开发人员完成了他们的更改,他们会将特性分支推送到远程代码库。
自动化合并: Merge Train 系统会检测到新的推送,并按照一定的顺序自动地将这些特性分支逐个合并到主分支上。
运行 CI/CD 流程: 每次合并后,自动化的 CI/CD 流程会运行,确保代码的质量和稳定性。
部署: 如果 CI/CD 流程通过了所有测试,代码会自动部署到相应的环境。
Merge Train 的优点包括:
- 持续集成:保证了代码的高质量,减少了合并导致的错误。
- 自动化:减少了手动合并的工作,提高了效率。
- 保持代码库整洁:通过自动化合并,保持了主分支的整洁和线性提交历史。
需要注意的是,使用 Merge Train 需要慎重考虑,特别是在多人协作的项目中。确保所有的 CI/CD 流程和自动化合并过程都被充分测试和配置,以避免潜在的问题。
如果源分支与主分支有冲突, 则需要在本地环境中手动处理.
假设当前正处于某个源分支上, 假设主分支名为 main:
# 拉取远程仓库所有分支
git fetch --all
# 将远程仓库主分支合并至本地的源分支
git merge origin/main
# ---- 解决冲突 ---- #
# ...
# ---- 解决冲突 ---- #
# 推送至远程仓库中的源分支
git pushINFO
MR 中的 目标分支 从技术上讲可以是任意分支,
但在 Trunk Based Development 中一般都将主分支作为 MR 的目标分支.
跳过 CI/CD
有些时候我们希望跳过 CI/CD 直接完成 MR 的合并 (例如只更新了文档).
此时可以通过 Git Push Options 来跳过 CI/CD.
# 推送至远程仓库时跳过 CI/CD
git push -o ci.skipINFO
💡若不想使用 Git Push Options, 则可修改 commit message, 添加 [ci skip] 或 [skip ci].
毕竟如果在 MR 中选择了 squash, 那就不用担心 commit message 被污染的问题.
因为 MR 合并后, 主分支上的 commit message 就是 MR 的标题, 与源分支的提交记录无关.
对应的 Merge Request 页面效果如下图:

WARNING
请确保当前代码仓库的 Settings / Merge Requests 设置中勾选了:
Skipped pipelines are considered successful

与其他功能的联系
添加引用
issue 可以通过 #xxx 的形式被引用 ( # 作为前缀),
merge request 与之类似, 可以通过 !xxx 的形式被引用 ( ! 作为前缀).
如果需要跨仓库引用, 则需要在前面添加仓库名称, 例如 project!123.
MR 的引用可以出现在很多地方, 例如 issue 和 MR 的所有可编辑区域.
并且如果 commit message 中包含 !xxx, 则可以直接点击跳转到对应的 MR 页面.

GitLab 提供了特殊语法来详细显示对于 MR 的引用: 在末尾加上 + 或 +s.
假设某个 MR 编号为 !1, 已经被合并, 分派给了 Yusong, milestone 为 0.1:
| 原文 | 显示效果 |
|---|---|
!1 | !1 (merged) |
!1+ | Resolve "支持动态分辨率" (!1 - merged) |
!1+s | Resolve "支持动态分辨率" (!1 - merged) • Yusong • 0.1 |
TIP
在 gitlab wiki 中可以使用这种方法来引用 merge request.
全局搜索
如果想要查询某个 MR, 则可以在 MR 浏览页面设置筛选条件.
然而笔者更推荐使用全局搜索, 使用快捷键 / 唤出, 然后直接搜索 MR 标题的关键字:

如果对以上搜索结果不满意, 可以按下 Enter 键进入全局搜索页面.
此时应当注意选择恰当的搜索类型, 例如下图中的搜索类型就是 Merge Requests.

CI/CD 流程控制
我们可以在 .gitlab-ci.yaml 中设置 CI/CD 管线的触发条件.
其中与 MR 有关的 $CI_PIPELINE_SOURCE 的值是 "merge_request_event".
## GitLab CI/CD 工作流控制
workflow:
rules:
## 通过 MR 发起的总会执行
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
## git push 时不执行
- if: $CI_PIPELINE_SOURCE == "push"
when: never
## 其他未指明的情况会执行
- when: always另外还能够在 CI/CD Pipeline 列表中看到他们与 MR 的关联:

关闭/关联 Issue
在 MR 的描述中可以使用 Closing Pattern 来自动关联或关闭 issue.
如果有还未合并的 MR 关联了某些 issue, 那么在 issue 列表中也能看到这种联系:

如果在 MR 中关闭或关联了 Issue, 那么在 Issue 内部也能看到相关的 MR:

代码仓库的设置
受保护分支
代码仓库的 Settings / Repository / Protected Branches 中可以设置受保护分支.
受保护分支默认不能 force push, 且需要足够权限才能 push 或者审核 MR.

如果严格执行 MR 流程, 受保护分支应该禁止 push, 只接受 MR.
也就是说上图中的 Allow to push and merge 应该选择为 Maintainer.
在开发者角色设置中, 大部分开发者都应该是 Developer, 少数开发者才能作为 maintainer.
上图中的 Allow to merge 的权限可以设置得宽松一些, Developer 角色也能够审核 MR.
只要 CI/CD 管线存在, 自动化测试覆盖率足够, 就没问题.
MR 的设置
在代码仓库的 Settings / Merge Requests 中可以设置:
- Merge Method 建议勾选
Fast-forward merge. - Merge Options 建议
全部勾选. - Squash commits when merging 建议勾选
Encourage. - Merge checks 建议勾选
Pipelines must succeed. - 两个 message template 建议使用:
%{title} (%{local_reference})
%{description}Resolve "支持动态分辨率" (!1)
Closes #22另外在 Settings / Repository / Branch Defaults 中可以设置:
- Branch name template 建议设置为
%{id}, 如下图.

INFO
Branch name template 默认的值为 %{id}-%{title}, 然而 %{title} 不接受中文字符.
而我们写 issue 时多半会选择使用中文标题, 因此没有必要保留默认的 %{title}.
通知消息
CI/CD 未通过
若推送至源分支后自动执行的 CI/CD 管线失败, 会收到 To-Do List 页面通知.
在左侧导航栏能看到需要被处理的 To-Do 数量.

可以通过 shift+t 快捷键进入 To-Do List 页面.

auto-merge 成功
auto-merge 成功后的邮件通知:
