GitHub の Branch Protection Rules 入門:main を壊さないための最初の設定

概要

「main への直接 push は禁止」「CI が通ってからマージする」といった運用ルールは、多くのチームで決められています。ただ、ルールを決めただけでは守られません。急いでいるときや、ローカルのブランチを取り違えたときに、事故は起きます。

GitHub の Branch Protection Rules(ブランチ保護ルール) を使うと、こうした運用ルールを GitHub 側で強制できます。

この記事では次の内容を扱います。

  • Branch Protection Rules で何を防げるか
  • 最初に入れるべき最小構成と、画面からの設定手順
  • GitHub Actions のステータスチェックを必須にする方法
  • gh api で設定をコード化する方法
  • 設定が効いているかの確認方法と、よくあるハマりどころ

環境

  • GitHub(github.com)
  • GitHub Actions(CI として利用)
  • GitHub CLI(gh)※コード化する場合のみ

> プランによる制約 > パブリックリポジトリでは GitHub Free でも利用できます。プライベートリポジトリで使う場合は、GitHub Pro / Team / Enterprise などの有料プランが必要です。

保護しないと何が起きるか

事故 影響
main への直接 push レビューも CI も通らないコードが入る。main への push で本番デプロイが動く構成なら、そのまま本番に出る
CI が落ちたままマージ テストが壊れた状態が main に残り、後続の PR がすべて巻き込まれる
git push --force 他のメンバーのコミットが履歴から消える
ブランチの誤削除 main / release ブランチ自体が消える

どの事故も、原因は「人の注意力に頼っていること」です。ルールを文書化しても、強制する仕組みがなければ、いずれ破られます。

最初に入れるべき最小構成

設定項目はたくさんありますが、まずは次の4つで大きな事故はほぼ防げます。

  1. Require a pull request before merging(PR 経由のマージを必須にする)
  2. Require status checks to pass before merging(CI の成功を必須にする)
  3. force push と削除を禁止する(デフォルトで禁止。許可しないこと)
  4. Do not allow bypassing the above settings(管理者も迂回できないようにする)

画面からの設定手順

  1. リポジトリの Settings を開く
  2. 左メニューの Branches を選ぶ
  3. Add branch protection rule(UI によっては "Add classic branch protection rule")をクリック
  4. Branch name pattern に main と入力
  5. 以下にチェックを入れて Create で保存する
[x] Require a pull request before merging
    [x] Require approvals(必要な承認数: 1)
    [x] Dismiss stale pull request approvals when new commits are pushed
[x] Require status checks to pass before merging
    [x] Require branches to be up to date before merging
    → Status checks that are required に CI のジョブ名を追加
[x] Require conversation resolution before merging
[x] Do not allow bypassing the above settings
[ ] Allow force pushes   ← チェックしない
[ ] Allow deletions      ← チェックしない

主な設定項目の意味

項目 内容
Require a pull request before merging 直接 push を禁止し、PR からのマージだけを許可する
Require approvals 指定した人数の承認がないとマージできない
Dismiss stale pull request approvals 承認後に新しいコミットが push されたら、承認を取り消す
Require review from Code Owners CODEOWNERS に書かれた担当者の承認を必須にする
Require status checks to pass 指定したチェック(CI)が成功しないとマージできない
Require branches to be up to date ベースブランチの最新を取り込んだ状態でチェックが通ることを求める
Require conversation resolution レビューコメントがすべて Resolve されるまでマージできない
Require signed commits 署名付きコミットだけを受け付ける
Require linear history マージコミットを禁止する(squash / rebase マージのみ)
Lock branch ブランチを読み取り専用にする
Do not allow bypassing the above settings 管理者を含め、誰も上記のルールを迂回できなくする

実装例

1. 必須チェックにする CI を用意する

# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  test:
    name: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test

必須チェックとして登録するのは、ワークフロー名ではなくジョブ名(name: を指定した場合はその値)です。上の例なら test を登録します。

必須チェックの検索候補に出てくるのは、そのリポジトリで最近実行されたチェックだけです。候補に出ない場合は、一度 PR を作って CI を動かしてから設定してください。

2. gh api で設定をコード化する

画面から設定すると、リポジトリが増えたときに設定がずれていきます。REST API を使えば、同じ設定を何度でも再現できます。

{
  "required_status_checks": {
    "strict": true,
    "checks": [
      { "context": "test" }
    ]
  },
  "enforce_admins": true,
  "required_pull_request_reviews": {
    "dismiss_stale_reviews": true,
    "require_code_owner_reviews": false,
    "required_approving_review_count": 1
  },
  "restrictions": null,
  "required_linear_history": true,
  "allow_force_pushes": false,
  "allow_deletions": false,
  "required_conversation_resolution": true
}

上の JSON を protection.json として保存し、次のコマンドで適用します。

gh api \
  --method PUT \
  -H "Accept: application/vnd.github+json" \
  repos/OWNER/REPO/branches/main/protection \
  --input protection.json
  • strict: true は「Require branches to be up to date」に対応します
  • enforce_admins: true は「Do not allow bypassing」に対応します
  • restrictions は push できるユーザーやチームを制限する項目です。Organization のリポジトリでのみ使えます。使わない場合も null を明示的に渡す必要があります

確認方法

設定内容を確認する

gh api repos/OWNER/REPO/branches/main/protection

直接 push が拒否されるか確認する

git switch main
git commit --allow-empty -m "test: branch protection"
git push origin main

保護が効いていれば、protected branch に関するエラーで push が拒否されます。確認が済んだら、ローカルの空コミットは取り消してください。

git reset --soft HEAD~1

PR 画面で確認する

PR を作ると、マージボタンの上に必須チェックと承認の状態が表示されます。条件を満たすまでマージボタンは押せません。

注意点

1. 必須チェックが「Expected — Waiting for status to be reported」のまま進まない

主な原因は次のとおりです。

  • 登録したチェック名とジョブ名が一致していない(ジョブ名を変えた後に設定を更新していない)
  • マトリクスジョブで、実際のチェック名が test (20) のようにパラメータ付きになっている
  • ワークフローの paths フィルタやジョブの if: 条件でジョブが実行されず、チェック結果が報告されない

最後のケースでは、必須チェックは「結果待ち」のまま止まります。パスで実行を分けているジョブを必須にしない、または常に実行される集約ジョブを1つ作ってそれだけを必須にする、といった対策があります。

2. 1人開発で承認を必須にすると、自分で詰む

GitHub では自分の PR を自分で承認できません。1人で開発する場合は、次の構成が現実的です。

  • Require a pull request は有効(直接 push を防ぐ)
  • Require approvals は無効
  • Require status checks は有効(CI だけは必ず通す)

3. 管理者は迂回できる(デフォルトでは)

「Do not allow bypassing the above settings」を有効にしないと、管理者権限を持つユーザーはルールを無視して push やマージができます。

4. Rulesets という新しい仕組みもある

GitHub には、Branch Protection Rules の後継にあたる Rulesets(Settings → Rules → Rulesets)もあります。複数のルールを重ねて適用できる、Organization 全体に一括で適用できる、といった特徴があります。多数のリポジトリをまとめて管理する場合は、Rulesets も検討するとよいでしょう。考え方は同じなので、本記事の内容はそのまま役立ちます。

まとめ

  • Branch Protection Rules は「運用ルールを GitHub に強制させる」ための機能
  • 最初は PR 必須・ステータスチェック必須・force push/削除禁止・管理者も迂回不可 の4点から始める
  • 必須チェックはジョブ名で登録する。paths フィルタとの組み合わせに注意する
  • 1人開発では承認必須を外し、CI 必須だけを残す
  • 設定は gh api でコード化しておくと、複数リポジトリでも揃えやすい

「気をつける」を「仕組みで防ぐ」に置き換えるのが、CI/CD の第一歩です。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。