👁

GitHubで保護ブランチを設定しよう

この章では、GitHubのRulesets機能を使ったmainブランチ保護の設定をハンズオン形式で学習します。これにより、直接Push禁止・必須ステータスチェックなど、CIをパスしたコードだけをマージさせる運用ができるようになります。

1. 本章の概要

1.1 本章の目的

GitHub Actionsで自動化を進めても、「mainブランチに直接Pushされた」「git push --forceで履歴が書き換わった」といった穴が残るとCIをすり抜けたコードが本番にデプロイされてしまいます。GitHubのRulesetsを使ってmainブランチに保護ルールを設定すると、これらの穴を塞ぎ、CIをパスしたコードだけがマージされる運用が実現できます。

1.2 ハンズオンの流れ

GitHubのRulesets機能を使って、mainブランチの削除禁止・Pull Request必須・必須ステータスチェック・Force Push禁止・Bypassの5つの保護ルールを設定します。ルールを違反する変更と満たす変更をそれぞれ試して、実際の動作を確認します。

1.3 事前準備

前提となる講座

この章では、以下の知識を前提としています。自信がない場合は先に関連講座を実施してみましょう。

講座名 必要な知識
Git入門 リポジトリ作成、コミット、Push、ブランチ操作など、Gitの基本操作

必要なツール

この章では、以下のツールを使用します。まだインストールしていない場合は、リンク先の手順に沿って準備をお願いします。

ツール名 関連箇所 理由
Visual Studio Code Visual Studio Codeのインストール ブランチ操作の対象となるコードを編集するエディタとして使用する
Git Git/GitHubのセットアップ 保護ブランチの動作を確認するローカル操作に使用する

必要なアカウント

この章では、以下のアカウントを使用します。まだ用意していない場合は、リンク先の手順に沿って準備をお願いします。

アカウント名 関連箇所 理由
GitHubアカウント Git/GitHubのセットアップ 保護ブランチを設定するリポジトリのホスティング先として使用する

2. 今回設定する保護ルール

今回のハンズオンでは、Rulesetsで以下のルールを設定します。

ルール 内容
Restrict deletions 指定したブランチの削除を禁止する
Require a pull request before merging 指定したブランチへの変更はPull Request経由のみに限定する
Require status checks to pass 指定したCIワークフローが成功しないとマージできない
Block force pushes git push --forceによる指定したブランチの履歴書き換えを禁止する
Bypass list Admin権限を持つユーザだけがPull Request画面からルールを例外的にバイパスできる

平常時は全員にルールを強制し、緊急時のみ責任者がバイパスする、という実務的な運用を体験できます。

📝 Rulesetsとは
Rulesetsは、GitHubがリポジトリ・Organizationのブランチ保護を柔軟に定義するために提供している仕組みです。GitHubは新規に保護設定を行う場合はRulesetsの利用を推奨しています。詳しくはAbout rulesetsに記載があります。

3. リポジトリの準備

保護ブランチの設定は、既存のリポジトリに適用するとこれまでの操作と混ざってしまい分かりづらくなります。このセクションでは、専用の新しいリポジトリを作成し、そこに最小限のファイルだけを配置した状態で進めます。

3.1 GitHubでの空リポジトリの作成

GitHubにログインし、右上の**+ボタンからNew repository**をクリックします。

以下の設定でリポジトリを作成します。

設定項目 設定の基準
Owner ご自身のGitHubアカウント 自分のアカウントで管理するため
Repository name devopscamp-branch-protection-handson ハンズオン用のリポジトリ名
Visibility Public GitHub Actionsの無料実行時間を気にせず使えるため
Initialize this repository with 何もチェックしない 後から自分でファイルをPushするため

Create repositoryをクリックしてリポジトリを作成します。

作成後、「Quick setup」の画面が表示されたら、ここでは何も操作せずに次のステップに進みます。

GitHubのリポジトリ一覧にdevopscamp-branch-protection-handsonが表示されていれば、ここまでの操作は完了です。

3.2 リポジトリのClone

作成したリポジトリをローカル環境にcloneします。ローカル環境で、Windowsであればコマンドプロンプト、Macであればターミナルを開きます。

以下のコマンドで、Git がインストールされていることを確認します。

git --version

以下のように Git のバージョンが表示されれば、インストールは確認できています。

git version 2.x.x

バージョンが表示されない場合は、Git/GitHubのセットアップ を先に実施してください。

インストールが確認できたら、下記のコマンドを実行します。<your-github-account-name>の部分はご自身のGitHubアカウント名に置き換えてください。

git clone https://github.com/<your-github-account-name>/devopscamp-branch-protection-handson.git

空のリポジトリなのでYou appear to have cloned an empty repository.という警告が表示されますが、問題ありません。

3.3 サンプルファイルの作成

Visual Studio Codeでdevopscamp-branch-protection-handsonフォルダを開きます。「ファイル」メニュー >「フォルダを開く」から、先ほどcloneしたフォルダを選択してください。

このセクションでは、保護ルールが動作することを確認するために2つのファイルを用意します。1つ目はREADME.md、2つ目はPRの際にREADME.mdの存在をチェックするワークフローファイルです。静的解析や自動テストのような本格的な内容は不要で、「単にREADME.mdがあるかどうか」というごく単純なチェックで保護ルールの動作を確認します。

まず、リポジトリ直下にREADME.mdを作成します。

devopscamp-branch-protection-handson/
└── README.md  ← このファイルを作成

作成したファイルに以下の内容を記述して保存します。

# devopscamp-branch-protection-handson

DevOps Campの保護ブランチハンズオン用リポジトリです。

次に、ワークフローファイルを作成します。.github/workflows/check-readme.ymlを作成します。

devopscamp-branch-protection-handson/
├── README.md
└── .github/
    └── workflows/
        └── check-readme.yml  ← このファイルを作成

作成したファイルに以下の内容を記述して保存します。

name: README Check

on:
  pull_request:
    branches:
      - main

jobs:
  check-readme:
    name: Check README exists
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v5

      - name: Check README.md exists
        run: |
          if [ ! -f README.md ]; then
            echo "Error: README.md does not exist"
            exit 1
          fi
          echo "README.md exists"

このワークフローは、Pull Requestが作成された際に動き、リポジトリ直下にREADME.mdがあれば成功、なければ失敗するという非常にシンプルなチェックを行います。本章ではこのワークフローを「必須ステータスチェック」として利用します。

3.4 初回コミットとPush

ローカルリポジトリに2つのファイルを配置できたので、GitHubにPushしてリモートリポジトリに反映させます。

Visual Studio Codeのターミナルを開き、以下のコマンドを1つずつ実行します。

ステージングします。

git add .

コミットします。

git commit -m "initial commit"

mainブランチにPushします。

git push origin main

GitHubのリポジトリページを更新すると、README.md.github/workflows/check-readme.ymlが表示されます。これが表示されていれば、ここまでの操作は完了です。

4. Rulesetの設定

mainブランチを保護するために、Rulesetを新しく作成します。Rulesetの設定画面はリポジトリのSettingsから開きます。

4.1 Ruleset作成画面の表示

GitHubのdevopscamp-branch-protection-handsonリポジトリのページを開きます。

Settingsタブをクリックします。

左メニューからRules > Rulesetsを選択します。

New rulesetをクリックし、ドロップダウンからNew branch rulesetを選択します。

4.2 基本設定

Rulesetの基本情報を入力します。

設定項目 設定の基準
Ruleset Name main-protection ルールセットを識別するための名前
Enforcement status Active ルールを有効化して即座に適用するため

Enforcement statusには、他にDisabled(ルールは存在するが無効)やEvaluate(ルール違反を記録するが強制はしない)があります。Evaluateは本番前の試運転に便利ですが、今回は動作をすぐに確認したいのでActiveを選びます。

4.3 Bypassリストの設定

Admin権限を持つユーザがPull Request画面から例外的にルールを突破できるよう、Bypassリストを設定します。

Bypass listセクションでAdd bypassをクリックし、Repository adminを選択します。

追加後、Modeを選択します。以下の設定にします。

設定項目 設定の基準
Bypass mode Pull requests Pull Request画面からのマージ時のみバイパスを許可する

Bypass modeには2種類あります。

モード 動作
Always 常にルールを無視できる。git pushでの直接Pushも許される
Pull requests Pull Requestのマージ操作時のみバイパスが有効。CLIでの直接Pushは引き続き拒否される

Alwaysを選ぶとadminがCLIでも自由にmainへPushできてしまいます。今回は「CLIでの直接Pushは全員禁止しつつ、Pull Request画面では緊急時にadminがバイパスできる」という実務に近い設定にしたいので、Pull requestsを選択します。

4.4 対象ブランチの指定

Rulesetは「どのブランチに対して適用するか」を指定する必要があります。今回はmainブランチに適用します。

Target branchesセクションでAdd targetをクリックし、Include default branchを選択します。これにより、リポジトリのデフォルトブランチ(main)がターゲットに追加されます。

Include default branchを使うことで、将来デフォルトブランチ名を変更した場合でも自動的に新しいデフォルトブランチに追従します。特定のブランチ名で固定したい場合はInclude by patternでブランチ名を指定します。

4.5 ルールの設定

次に、実際に強制するルールを選びます。画面下部のRulesセクションで、以下の項目にチェックを入れていきます。

Restrict deletions

この項目にチェックを入れると、mainブランチの削除が禁止されます。誤操作でmainブランチが消えることを防ぐ、基本中の基本となる設定です。

Require a pull request before merging

この項目にチェックを入れると、mainブランチへの変更はPull Request経由でのみ行えるようになります。これが「mainへの直接Push禁止」を実現する最も重要な設定です。

この項目を有効にすると、追加の設定が表示されます。今回は以下のように設定します。

設定項目 設定の基準
Required approvals 0 個人リポジトリでは自分自身のPRを承認できないため、今回は承認なしで進める
Dismiss stale pull request approvals when new commits are pushed OFF 承認数を0にしているため
Require review from Code Owners OFF 個人リポジトリではCODEOWNERSの動作確認ができないため、今回は無効
Require approval of the most recent reviewable push OFF 承認数を0にしているため
Require conversation resolution before merging ON Pull Requestのコメントが未解決のままマージされないようにするため
💡 ポイント
実際にはRequired approvalsを1以上に設定し、「他の開発者の承認がないとマージできない」状態にすることが一般的です。またRequire review from Code Ownersを有効にすると、特定のファイル・ディレクトリの変更時に特定のレビュアーのApproveを強制できます。今回は個人リポジトリのため動作確認はできませんが、チーム開発では必須の設定です。

Require status checks to pass

この項目にチェックを入れると、指定したCIワークフローが成功していないとマージできなくなります。これが「CIをパスしたコードだけがmainに入る」という品質ゲートの中核的な設定です。

チェックを入れるとAdd checksボタンが表示されるので、クリックします。

検索窓にCheck README existsと入力し、候補に表示される**+ Add Check README exists**(Any source)を選択して追加します。

💡 ポイント
Add checksでは「すでに実行されたことのあるチェック名」から選ぶか、任意の名前を手動で入力して追加できます。手動で追加する場合、ワークフローがまだ一度も実行されていなくても設定できます。入力する名前は、ワークフローのジョブ名(jobs.<job-id>.nameで指定した値)と一致させる必要があります。今回はcheck-readme.ymlname: Check README existsと指定しているため、Check README existsと入力します。

Require branches to be up to date before mergingONにします。有効にすると、mainが更新されるたびに作業用ブランチへ最新mainを取り込んでからでないとマージできなくなり、マージ後にmainで予期せぬ競合が発生することを防げます。

Do not require status checks on creationOFFのままで進めます。このオプションは、Rulesetの対象パターンにマッチするブランチの初回作成Pushで、必須ステータスチェックの要求を免除する設定です。feature/*のようにパターンで対象を広げる運用では、ワークフローが一度も走っていない新規ブランチの初回Pushが拒否されるのを防ぐために有効化しますが、今回はRulesetの対象をmainのみに限定しているためOFFのままで挙動に影響はありません。

Block force pushes

この項目にチェックを入れると、git push --forceによるmain履歴の書き換えが禁止されます。うっかりforce pushで過去のコミットを失う事故を防ぐ、もう一つの重要な設定です。

4.6 Rulesetの作成

必要な項目をすべて設定したら、画面下部のCreateボタンをクリックします。

作成後、Rulesetの詳細画面が表示されます。main-protectionActive状態で表示されていれば、ここまでの操作は完了です。

5. 保護ルール違反の確認

設定した保護ルールが本当に機能するか、わざと違反してみて確認します。

5.1 違反1: mainブランチへの直接Push

保護ルールが設定された状態で、mainブランチに直接Pushしてみます。

Visual Studio CodeでREADME.mdを開き、末尾に次の1行を追加します。

保護ルール適用後に直接Pushするテストです。

保存したら、ターミナルで以下のコマンドを1つずつ実行します。

git add .
git commit -m "test: direct push after protection"
git push origin main

今回はPushが拒否されるはずです。以下のようなエラーメッセージが表示されます。

remote: error: GH013: Repository rule violations found for refs/heads/main.
remote: 
remote: - Changes must be made through a pull request.
remote: 
To https://github.com/<your-github-account-name>/devopscamp-branch-protection-handson.git
 ! [remote rejected] main -> main (push declined due to repository rule violations)
error: failed to push some refs to 'https://github.com/...'

Changes must be made through a pull request.というメッセージが表示されていれば、Rulesetが正しく動作しています。Bypass modeをPull requestsにしているため、admin権限を持っていてもCLIでの直接Pushは拒否されます。

5.2 違反2: 必須ステータスチェックの失敗

次に、「CIが失敗しているPull Requestはマージできない」ことを確認します。今回のワークフローは「README.mdが存在しない場合に失敗」するよう作ってあるため、わざとREADME.mdを削除したPull Requestを作成して、チェックを失敗させます。

作業用ブランチの作成とREADME.mdの削除

先ほど確認したように、mainブランチへの直接Pushは拒否されます。一方で、mainブランチ以外の作業用ブランチであれば通常通りPushでき、そこからPull Requestを作ってmainにマージする流れになります。ここでは、READMEを削除する作業用ブランチを切ってPushします。

作業用ブランチを作成します。

git switch -c test/delete-readme

Visual Studio CodeのエクスプローラーでREADME.mdを右クリックし、「削除」を選択して削除します。

変更をステージングします。

git add .

コミットします。

git commit -m "test: delete README to trigger failure"

Pushします。

git push origin test/delete-readme

Pull Requestの作成

GitHubのリポジトリページを開き、Pull requestsタブからNew pull requestをクリックします。

base: main, compare: test/delete-readmeでPull Requestを作成します。

ワークフローの実行結果とマージボタンの確認

作成したPull Requestの画面を開くと、README Check / Check README existsのワークフローが実行されます。しばらくすると実行結果が**失敗(赤バツ)**になります。

Run Check README.md existsのステップを開くと、Error: README.md does not existというメッセージが出力されてエラー終了していることが確認できます。

Pull Requestの画面を下にスクロールすると、マージボタンの周辺にRequired status check "Check README exists" is expected.(または類似の赤いメッセージ)が表示されます。Merge pull requestボタンは灰色で無効化された状態になっており、ステータスチェックが失敗している間はマージできません。

これが「CIをパスしないコードはmainに入れない」という必須ステータスチェックの効果です。

5.3 違反3: Admin bypassの動作確認

違反2の状態で、Admin権限を持つユーザがPull Request画面から例外的にマージできることを確認します。

今のPull Request画面に戻ります。マージボタンの近くに**Merge without waiting for requirements to be met (bypass rules)**というチェックボックス、または「管理者としてルールをバイパスしてマージする」といった趣旨のオプションが表示されています。

💡 ポイント
Admin bypassのUI(チェックボックスやボタン)の文言・形式は、GitHubのUI更新によって変わることがあります。「bypass rules」「管理者として強制マージ」「Required checksを無視してマージ」など、類似の表現を探してください。表示されない場合は、RulesetのBypass listRepository adminPull requestsモードで追加されているかを確認してください。

このチェックボックスをオンにするとMerge pull requestボタンが有効化され、本来マージできないはずのPull Request(README.mdを削除したPull Request)を強制的にマージできます。

ただし、今回はマージせずに次のステップに進んでください。実際にマージするとmainブランチのREADME.mdが消え、このあとの操作で混乱する可能性があるためです。Admin bypassの動作は「バイパス用のUIが存在していること」の確認のみで十分です。

実際には、緊急修正(hotfix)を最速で反映したい場合や、CI環境の障害で必須チェックが動かない場合などに、責任者がbypassを使います。bypass操作はGitHubのAudit logに残るため、誰がいつbypassしたかを後から追跡可能です。

5.4 状態の復元

違反確認が終わったので、状態を元に戻します。作成したPull Requestをそのままにしておくと、後続の操作で邪魔になる可能性があるためです。

GitHub画面でPull Requestを開き、Close pull requestをクリックしてクローズします(マージはしません)。

ローカルでは、mainブランチに戻します。

git switch main

不要になった作業用ブランチを削除します。

git branch -D test/delete-readme

ここまで完了していれば、違反確認は完了です。

6. 保護ルールを満たす変更でのマージ

保護ルールに違反する変更が拒否されることを確認したので、最後に保護ルールを満たす変更であれば通常通りマージできることを確認します。作業用ブランチで変更してPull Requestを作り、CIを成功させてマージするという、実務で日常的に行うフローを一度体験します。

6.1 作業用ブランチの作成とREADME.mdの編集

作業用ブランチを作成します。

git switch -c feature/update-readme

Visual Studio CodeでREADME.mdを開き、末尾に次の1行を追加します。

保護ルールを満たす変更のテストです。

変更をステージングします。

git add .

コミットします。

git commit -m "feat: add line to README"

Pushします。

git push origin feature/update-readme

6.2 Pull Requestの作成とマージ

GitHubのリポジトリページを開き、Pull requestsタブからNew pull requestをクリックします。

base: main, compare: feature/update-readmeでPull Requestを作成します。

Pull Requestの画面で、README Check / Check README existsのワークフローが実行されます。今回はREADME.mdを削除していないため、しばらくすると実行結果が成功(緑チェック)になります。Pull Requestの画面を下にスクロールすると、今度はMerge pull requestボタンが緑色で有効になっています。

Merge pull requestをクリックし、Confirm mergeでマージを確定します。マージが完了すると、Pull Requestの上部にMergedバッジが表示されます。

「保護ルールを満たすPull Requestであれば通常通りマージできる」ことが確認できました。

7. その他の保護ルールの紹介

今回のハンズオンでは個人リポジトリでも実際に試せる範囲のルールを中心に扱いましたが、実際にはさらに多くの保護ルールを組み合わせます。ここでは代表的なものを紹介します。

ルール 内容 実務での使いどころ
Required approvals Pull Requestに指定した人数以上のApproveがないとマージできない チーム開発で「最低1名のレビュー必須」を強制する
Require review from Code Owners 変更ファイルに対応するCODEOWNERSに書かれたレビュアーの承認を必須化 「特定のディレクトリはインフラチームの承認必須」といった役割ベースのレビュー
Dismiss stale pull request approvals 新しいコミットがPushされた時に過去のApproveを自動で取り消す レビュー後の変更を見逃さないようにする
Require signed commits GPGまたはSSH署名付きのコミットだけを許可 コミットの改ざん検出・作者の保証が必要な高セキュリティ環境
Require linear history マージコミットを作らず、SquashまたはRebaseマージのみ許可 mainの履歴を一直線に保ち、ログを読みやすくする
Require branches to be up to date before merging mainの最新を取り込んでいないブランチはマージ不可 マージ後に予期せぬ競合が発生することを防ぐ

詳細な設定方法は、Available rules for rulesetsに記載があります。

💡 ポイント
これらのルールは「厳しくすればするほど安全」というわけではありません。例えばRequire branches to be up to dateを有効にすると、大規模チームではPRがマージされるたびに他のPRの再実行が必要になり、運用負荷が上がります。組織の規模やリスク許容度に応じて、必要なルールを取捨選択することが重要です。

8. 不要リソースの削除

ハンズオンが完了したので、作成したRulesetとリポジトリを削除します。

8.1 Rulesetの削除

GitHubのリポジトリページからSettings > Rules > Rulesetsを開きます。

main-protectionの右側にあるメニューからDeleteをクリックします。

確認画面でDelete rulesetをクリックすると、Rulesetが削除されます。

Rulesetsの一覧からmain-protectionが消えていれば削除完了です。

8.2 リポジトリの削除(任意)

作成したdevopscamp-branch-protection-handsonリポジトリ自体が不要な場合は、併せて削除します。残しておきたい場合、この手順は省略して問題ありません。

GitHubのdevopscamp-branch-protection-handsonリポジトリのページでSettingsタブをクリックし、画面を一番下までスクロールします。

Danger ZoneのセクションにあるDelete this repositoryボタンをクリックします。

確認画面の案内に沿ってリポジトリ名を入力し、削除を実行します。

リポジトリ一覧からdevopscamp-branch-protection-handsonが消えていれば、削除完了です。

9. まとめ

この章では、GitHubのRulesets機能を使ってmainブランチを保護する実務的な運用ルールを体験しました。

  • mainブランチへの直接Pushを禁止し、変更はPull Request経由に限定できる
  • 必須ステータスチェックにより、CIをパスしないコードのマージを防げる
  • Force pushとブランチ削除の禁止により、mainの履歴を安全に保てる
  • Admin bypassを使うことで、緊急時に責任者が例外的にルールを突破できる
  • Required approvalsCODEOWNERS など、チーム開発で活用される追加ルールの位置づけも整理できる

次の章では、TerraformのCI/CDパイプラインを構築する流れをハンズオン形式で体験します。

この教材は役に立ちましたか?

いいねをたくさんいただけると、制作者の励みになり、より多くのセクションが作れるようになります。

感想を一言(任意)

いただいたコメントは次の制作のヒントになります。ぜひお気軽にご投稿ください。

このコメントは他の受講生には公開されません。DevOps Camp運営が、教材改善のために確認します。

0 / 2000