PR 和 MR 有什么区别
一旦开始和别人协作写代码,很快就会听到这样的话:「我提了个 PR。」或者「帮我看下这个 MR。」换了公司之后发现,原来叫 PR 的东西这里大家都叫 MR,难免会愣一下,怀疑自己学过的东西在这里是不是不管用了。
先说结论:它们是同一个东西。只是 GitHub 叫它 Pull Request,GitLab 叫它 Merge Request。Bitbucket、Gitea 及其分支 Forgejo、Azure DevOps 都在 Pull Request 这一边。无论哪一边,做的事情完全一样:把你的分支和目标分支之间的差异显示出来,让同事对着差异留言评审,达成一致之后按下按钮合并。
名字分道扬镳,是因为它们从相反的两端描述同一件事。pull 是接收方的动作,意思是「请把我的分支拉过去」。merge 是这个请求实现之后的结果,意思是「请合并它」。工具里至今还留着更早的形态:gh pr create 的帮助文本会提出替你 fork 目标仓库。向别人的仓库贡献代码这幅图景,就嵌在这个词里。
所以在实际工作中两个词可以混用,没有人会听不懂。在用 GitLab 的团队里说「我提了个 PR」,大家照样明白你的意思。术语本身几乎从来不是真正的问题。
| 平台 | 叫法 | URL 路径 |
|---|---|---|
| GitHub | Pull Request (PR) | /pull/123 |
| GitLab | Merge Request (MR) | /-/merge_requests/123 |
| Bitbucket | Pull Request (PR) | /pull-requests/123 |
| Gitea · Forgejo | Pull Request (PR) | /pulls/123 |
| Azure DevOps | Pull Request (PR) | /pullrequest/123 |
Git 本身也有 pull request — git request-pull

这里有一个大多数人会漏掉的事实:Git 自己也有 pull request。不是比喻,是真的存在一个叫这个名字的命令。
打开终端敲 git request-pull。不带参数时它会打印用法。给它一个起点和一个仓库地址,它就会把你从那个起点之后做的提交整理成一段摘要,请对方把这些改动拉过去。用官方文档的说法,它生成的是一份请求上游项目把你的改动拉进他们仓库的说明。
然而这就是全部了。这个命令产出的只有屏幕上的文字。不会弹出评审界面,没有批准按钮,没有打开或关闭的状态,没有留言的位置,也不会记录谁批准过。它甚至不会被发送到任何地方,只是输出到标准输出而已。
因为它本来就是这么用的。在 Linux 内核那种基于邮件列表的开发方式里,你把这段输出复制到邮件正文里发给维护者。维护者读完之后自己执行 git pull。评审则通过邮件回复来进行。
# 确认命令存在(会打印用法)
git request-pull
# 实际形态
git request-pull <起点> <仓库URL> [<终点>]
# 例:请对方拉走你分支上 main 之后的提交
git request-pull main https://github.com/me/repo.git my-branch
# 完整文档
git help request-pull那我们每天用的 PR 和 MR 到底是什么
把前面两节接起来,答案就出来了。我们每天打开的那个界面并不是 Git 的功能,而是托管服务在 Git 之上搭建的网页对象。
Git 的工作到计算两个分支之间的差异为止。把这个差异画成网页、允许逐行留言、记录谁批准过、挂上自动化检查、只有在条件满足时才让合并按钮可用 —— 这些全都是 GitHub、GitLab 这类产品的职责。所以 PR 和 MR 才会有编号,有打开和关闭的状态,有负责人和标签。这些概念在 Git 里一个都没有。
这个区分在实践中为什么重要?因为只靠本地的 Git 知识,你无法知道团队的协作规则。无论你多熟悉 git merge,你们团队需要几个人批准才能合并,这件事写在平台设置里,而不在 Git 里。而恰恰是这些设置,在 GitHub 和 GitLab 上长得不一样。
真正的差别在终端里显形 — gh pr 与 glab mr
看清两个平台差别最快的办法,是把两家官方 CLI 的子命令列表并排放在一起。一个产品把什么当作一等概念,会直接体现在它的命令结构里。
在 GitHub CLI 里,批准是评审命令的一个选项:gh pr review --approve。批准、留言、要求修改都是 review 这一个命令的选项。而在 GitLab CLI 里,批准本身就是一个独立命令。有 glab mr approve,旁边还有查询谁有批准资格的 approvers,以及撤回自己已给出批准的 revoke。
CI 状态则正好相反。GitHub 把流水线结果挂在 PR 上,敲 gh pr checks 就能看到这个 PR 的检查结果。GitLab 没有 glab mr checks 这样的东西,取而代之的是一个独立的顶层命令层 glab ci,流水线在那里处理。
还有几个命令的气质明显不同。GitLab 有把源分支重新落到目标分支之上的 glab mr rebase,以及直接从议题创建 MR 的 glab mr for。GitHub 有把分支更新到最新的 gh pr update-branch、回滚已合并 PR 的 gh pr revert,以及把草稿标记为可评审的 gh pr ready。
| 想做的事 | GitHub (gh) | GitLab (glab) |
|---|---|---|
| 创建 | gh pr create | glab mr create |
| 查看列表 | gh pr list | glab mr list |
| 拉到本地 | gh pr checkout | glab mr checkout |
| 查看改动 | gh pr diff | glab mr diff |
| 批准 | gh pr review --approve | glab mr approve |
| 撤销批准 | (重新提交评审) | glab mr revoke |
| 查询可批准人 | (无专用命令) | glab mr approvers |
| 查看 CI 状态 | gh pr checks | glab ci(独立层级) |
| 合并 | gh pr merge | glab mr merge |
# GitHub CLI
gh pr --help
# GitLab CLI
glab mr --help
# 看批准挂在哪里
gh pr review --help # 以 --approve 选项出现
glab mr approve --help # 以独立命令出现第一次提交 — GitHub 和 GitLab 两边都试一遍

「只是名字不同、流程一样」这个说法,实际敲一遍命令就能立刻验证。下面的顺序在两边完全对称:把命令里的 pr 换成 mr、gh 换成 glab,就是 GitLab 的做法。
首先安装 CLI 并登录。在 macOS 上两者都可以用 Homebrew 安装。登录分别是 gh auth login 和 glab auth login,会打开浏览器完成账号认证。这一步只需要做一次。
接下来就是平时的 Git 操作:创建分支、提交、推送到远端。到这里为止 GitHub 和 GitLab 没有任何差别,因为这部分是纯粹的 Git。
最后创建 PR 或 MR。两边的 CLI 都支持 --fill 选项,会从提交信息里自动填好标题和正文。如果还没准备好接受评审,加上 --draft 以草稿形式提交。草稿打开时合并按钮是锁住的,适合提前分享还在进行中的工作。
提交之后不用打开浏览器,在终端里就能查看。加上 --web 会在浏览器中打开,只敲 view 则会把标题、正文和状态打印在终端里。
# 首次设置
brew install gh
gh auth login
# 干活
git switch -c fix-login
git commit -am "修正登录失败时显示的提示"
git push -u origin fix-login
# 创建 PR(标题和正文自动取自提交)
gh pr create --fill
# 以草稿形式创建
gh pr create --fill --draft
# 查看后合并
gh pr view
gh pr checks
gh pr merge --squash# 首次设置
brew install glab
glab auth login
# 干活
git switch -c fix-login
git commit -am "修正登录失败时显示的提示"
git push -u origin fix-login
# 创建 MR(标题和正文自动取自提交)
glab mr create --fill
# 以草稿形式创建
glab mr create --fill --draft
# 查看后合并
glab mr view
glab mr approve
glab mr merge迁移时真正需要重写的文档
那么团队从 GitHub 迁到 GitLab,或者反过来,真正要动手的地方在哪里?不是术语表。把 PR 改口叫 MR,大概一天就习惯了。
需要重写的是批准规则文档:合并前需要几个人批准,代码所有者的批准是否必需,有新提交推上来时既有批准是否失效,批准能不能撤回。这些规则在两个平台上散落在不同的名字和不同的界面里。前面看到的 CLI 结构就是这个差距的摘要 —— GitLab 把查询可批准人和撤销批准都做成独立命令,说明这个概念在产品内部被建模得有多厚。
还有一件事值得一并确定:合并策略。默认把提交压成一个(squash)、先变基再合并、还是保留合并提交,每个团队都要选一个,而这个设置在各平台上的名字和位置也不一样。趁着迁移,把它一起写进文档。
总结起来是这样:PR 和 MR 是同一个东西。不同的不是名字,而是挂在这个东西上的规则,而这些规则握在平台手里,不在 Git 手里。
