PR と MR は何が違うのか
人と一緒にコードを書き始めると、すぐにこう言われる。「PR 出しました」あるいは「MR のレビューお願いします」。転職した先で、これまで PR と呼んでいたものを皆が MR と呼んでいると、一瞬手が止まる。自分が覚えたことはここでは通用しないのだろうか、と。
結論から言うと同じものだ。GitHub が Pull Request、GitLab が Merge Request と呼んでいるだけである。Bitbucket も、Gitea とそのフォークである Forgejo も、Azure DevOps も Pull Request の側だ。どちらであれやることは変わらない。自分のブランチとマージ先ブランチの差分を画面に出し、同僚がその差分にコメントしてレビューし、合意できたらボタンを押してマージする。
名前が分かれたのは、同じ出来事を反対側から見ているからだ。pull は受け取る側の動作で、自分のブランチを取り込んでほしいという意味になる。merge はその要求が果たされた結果で、マージしてほしいという意味だ。実際、gh pr create のヘルプには今でも対象リポジトリをフォークするかどうか尋ねる記述が残っている。他人のリポジトリに貢献するという構図が言葉に染み込んでいるわけだ。
だから実務では混ぜて使っても通じる。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 が承認できる人の一覧表示と承認の取り消しを独立したコマンドとして持っているということは、その概念が製品の中でそれだけ厚く扱われているという意味だ。
もう一つ、同時に決めておきたいのがマージの方式である。コミットを一つにまとめるスカッシュを既定にするのか、リベースしてからマージするのか、マージコミットを残すのか。チームごとに決めるものだが、この設定もプラットフォームごとに名前と場所が違う。移行のついでに文書に書き足しておくとよい。
まとめるとこうなる。PR と MR は同じものだ。違うのは名前ではなく、そのものに取り付けられたルールであり、そのルールは Git ではなくプラットフォームが握っている。
