GitHubとGitLabのBranch Protection Rules設定:コードレビューをスキップさせずmainブランチを守る

Git tutorial - IT technology blog
Git tutorial - IT technology blog

午前2時。Slackの通知で飛び起きた:「Production down。誰かがmainに直接pushした。」

初めてではなかった。当時のチームは8人で、誰も意図的に壊そうとしていたわけではない。でもガードレールがなければ、こういう事故は日常茶飯事だ。焦った状態でgit push --force origin mainを一発叩くだけで、他の人のコミットを上書きしたり、テストされていないコードをそのままproductionにデプロイしてしまう。あの夜はロールバックに3時間かかった。

Branch Protection Rulesがそのループからチームを救い出してくれた。ただし、GitHubとGitLabの実装はかなり異なる。設定を間違えると、何も設定しないよりも辛い結果になることもある。

アプローチの比較:GitHub vs GitLab

どちらのプラットフォームもブランチ保護機能を持っているが、設計思想がまったく異なる。GitHubは細かいアクション単位でのコントロールを選択しており、ブロックしたい操作を一つひとつチェックする形式だ。GitLabはロールベースで、誰が何をできるかを定義する方式を採用している。

GitHub Branch Protection Rules

Settings → Branches → Add branch protection ruleから設定する。パターン構文は柔軟で、mainrelease/*v[0-9]*なども使用できる。

知っておくべき重要なオプション:

  • Require a pull request before merging — PRの作成を必須化し、直接pushを即座にブロックする
  • Require approvals — 最低限のレビュアー数を設定。通常は1〜2名
  • Dismiss stale pull request approvals when new commits are pushed — 新しいコミットが追加されると承認が自動的にリセットされる。これは多くの人が思う以上に重要だ。これがなければ、レビュアーが承認した後に開発者が5つのファイルを追加してそのままマージしても、誰も気づかない
  • Require review from Code OwnersCODEOWNERSファイルに記載されたメンバーのみが承認権限を持つ
  • Require status checks to pass before merging — CIがグリーンでなければマージできない
  • Restrict who can push to matching branches直接pushできるユーザー/チームをホワイトリストで管理する
  • Allow force pushes / Allow deletions — デフォルトはオフ。特別な理由がない限り有効にしないこと

GitLab Protected Branches

GitLabの設定はSettings → Repository → Protected branchesにある。GitHubのように個々のアクションをブロックするのではなく、GitLabはロールを使って、誰がpushでき、誰がmergeでき、誰がforce pushできるかを定義する:

  • Allowed to merge: No one / Developers + Maintainers / Maintainers
  • Allowed to push and merge: No one / Developers + Maintainers / Maintainers
  • Allowed to force push: オン/オフで切り替え
  • Code owner approval required: CODEOWNERSファイルがある場合に有効化

見落とされがちな点として、GitLabのMerge request approvalsSettings → Merge requestsにあり、Protected branchesとは完全に分離している。これが最も混乱を招くポイントだ。初めて設定する人はProtected branchだけ設定して完了したと思い込んでしまうことが多い。

実際のメリット・デメリット

GitHub:わかりやすいが、落とし穴も多い

すべてが一つの画面に収まっており、見通しがよい。しかし最もよくある落とし穴は、Require approvals: 1を有効にしてもDismiss stale approvalsを忘れてしまうことだ。レビュアーが承認した後、開発者が5つのファイルを追加してそのままマージしても、誰も追加されたコードをレビューしない。

もう一つのマイナス点として、GitHub Freeのプライベートリポジトリではいくつかのオプションが制限される。具体的には、Require review from Code OwnersはGitHub ProまたはTeamプランでのみ利用可能だ。

GitLab:大規模チームにより適した設計

pushとmergeを別々に設定できるのがGitLabの強みだ。ワークフローに応じて、開発者はmergeはできるが直接pushはできない、あるいはその逆にすることができる。GitLab Free(セルフホスト)では、ほぼすべての機能が利用可能だ。弱点としては、approval rulesが2〜3箇所に分散しており、初回設定時に見落としやすいことが挙げられる。

適切な設定の選び方

実際のチームで両方を試した結果、チームの規模別のおすすめ設定を以下に示す:

  • 小規模チーム(2〜5人)でGitHubを使用する場合:Require PR + 1 approval + dismiss stale + CI check。シンプルで十分な機能を備え、オーバーヘッドがない。
  • 中規模チーム(6〜15人):CODEOWNERSを追加し、重要なフォルダ(src/infra/)にCode Ownerレビューを要求する。GitHubでもGitLabでも対応可能。
  • セルフホストGitLab、大規模チームロールベースの設定が特に効果を発揮する。Maintainerのみがmergeできるように設定し、Developerの直接pushを禁止する。

現在の8人チームではGitHubで1 approval + dismiss stale + CI checkを使用しており、これで十分だ。全員にPRを作成してお互いにレビューする必要があるため、好き勝手にpushする状況がなくなり、マージコンフリクトが大幅に減少した

ステップごとの導入手順

GitHub:mainのBranch Protection設定

Settings → Branches → Add branch protection ruleにアクセスし、以下を入力する:

Branch name pattern: main

以下のオプションにチェックを入れる:

✅ Require a pull request before merging
   ✅ Require approvals: 1
   ✅ Dismiss stale pull request approvals when new commits are pushed
   ✅ Require review from Code Owners (CODEOWNERSがある場合)
✅ Require status checks to pass before merging
   ✅ Require branches to be up to date before merging
✅ Do not allow bypassing the above settings
❌ Allow force pushes  (無効のまま)
❌ Allow deletions     (無効のまま)

レビュアーを自動でアサインしたい場合は、.github/CODEOWNERSファイルを作成する:

# File: .github/CODEOWNERS

# デフォルト:すべてのファイルに@lead-devsの承認が必要
*  @your-org/lead-devs

# Infrastructure:@ops-teamのみが承認可能
/infra/  @your-org/ops-team
/docker/ @your-org/ops-team

# Frontend:frontendチームが自己レビュー
/src/components/ @your-org/frontend-team

mainへの直接pushでテストしてみる:

git checkout main
echo "test" >> README.md
git add . && git commit -m "test direct push"
git push origin main
# 期待される出力:
# remote: error: GH006: Protected branch update failed for refs/heads/main.
# remote: error: At least 1 approving review is required by reviewers with write access.

ブロックされれば成功だ。

GitLab:Protected Branch + Approval Rules

ステップ1:Settings → Repository → Protected branchesにアクセスする:

Branch: main
Allowed to merge: Maintainers
Allowed to push and merge: No one
Allowed to force push: OFF
Code owner approval required: ON (CODEOWNERSがある場合)

ステップ2:Settings → Merge requests → Merge request approvalsにアクセスする。この設定は別の場所にあるため、忘れずに確認すること:

✅ Prevent approval by author
✅ Prevent approvals by users who add commits
✅ Remove all approvals when commits are added

Approval rules:
  Rule name: "Require 1 approval"
  Approvals required: 1
  Eligible approvers: [グループまたは特定のユーザーを選択]

ステップ3:MRを作成し、承認なしでmergeを試みる。GitLabが「Approval rules not satisfied」というメッセージでブロックすれば、設定は正しく完了している。

緊急時の対応(Hotfix)

バイパスが必要な状況も出てくる。productionが障害中で、レビューを待つ余裕がない場合だ。その場合でも、protection rulesを無効化してはならない。別の方法がある:

GitHub:Settings → Branchesに移動し、管理者向けにAllow force pushesを一時的に有効化する。pushが完了したら、10分以内に無効に戻すこと。

GitLab:Allowed to pushを「No one」から「Maintainers」に変更し、hotfixをpushしたら元に戻す。

重要なのは、バイパスのたびに記録を残すことだ。私はSlackチャンネル#git-bypass-logを使用しており、バイパスするたびに誰が、なぜ、いつ行ったか、そして設定を元に戻したかどうかを明記したメッセージを投稿している。3ヶ月後、そのチャンネルには4件しかメッセージがなかった。それは良いサインだ。

見落とされがちな設定

以下の4つは、チームで少なくとも一度は見落とした設定だ:

  • 「Require branches to be up to date」(GitHub) — マージ前にrebaseを強制し、3日前に作成したPRでmainにすでに20件の新しいコミットがある場合の暗黙的なコンフリクトを防ぐ
  • 「Do not allow bypassing the above settings」(GitHub) — 管理者でもPRを経由する必要がある。過剰に見えるかもしれないが、インシデントの95%がここから発生する:管理者が急いでバイパスしてしまうのだ
  • 「Prevent approvals by users who add commits」(GitLab) — ルールの抜け穴を防ぐ:小さなコミットを追加して自分で承認し、mergeをアンロックするという行為をブロックする
  • Require status checks(GitHub) — CIがまだない場合は有効にする必要はない。有効にしたのに通過するチェックが一つもなければ、mergeが永久にブロックされ、理由も不明になる

Branch protectionは万能薬ではない。事故を防ぐことはできるが、レビュアーが実際にコードを読まなければ、品質の低いコードは防げない。ただし私のチームでは、全員にPRを作成させて1件の承認を待つよう強制するだけで、コメントを通じてコミュニケーションする習慣が自然と生まれ、レビューの質が上がった。そして、あの午前2時のインシデントは二度と起きていない。

Share: