単独メンテナーの PR マージ儀式
ブランチ保護と enforce_admins のルールが単独メンテナーをデッドロックさせてしまう場合に、プルリクエストを自分でマージするための儀式。
プロジェクトのブランチ保護不変条件のもとで PR を自分でマージするための正準リファレンス。 メンテナーが書き込みアクセスを持つ唯一のコントリビューターである場合、GitHub のブランチ保護ルール
enforce_admins=true+required_approving_review_count=1が自己マージのデッドロックを生み出します。本ページはその解決方法を文書化します。
1. デッドロック
リポジトリの main ブランチには次の保護セットが設定されています。
required_pull_request_reviews.required_approving_review_count: 1enforce_admins: true
これらのルールは複数メンテナーのシナリオでは正しいものです(すべての変更は同僚レビューを経た PR を通じて main に到達し、管理者も例外ではありません)。しかし単独メンテナーにはデッドロックを生じさせます。GitHub のポリシーは自分が作成した PR への自己承認を許可せず、enforce_admins=true がレビュー承認要件に対する gh pr merge --admin のオーバーライドをブロックするためです。
解決パスは 3 つあります。
| パス | 使うべき場合 |
|---|---|
| 一時切り替え儀式(本リポジトリの正準的手法) | 単独メンテナーの自己マージのデフォルト。マージウィンドウ外では保護の不変条件を維持する |
| 書き込みアクセスを持つ CI ボットのアイデンティティを用意する | 複数メンテナーのオンボーディングが目前に迫っている場合にのみ採用する。ボット承認は保護が意図する人間によるレビューを損なう |
required_approving_review_count: 0 を恒久的に設定する | リポジトリが無期限に単独メンテナーのままであり続ける場合にのみ採用する。保護のレビュー意図を覆す |
本リポジトリは一時切り替え儀式を正準パスとして批准しています。他の 2 つは文書化された緊急脱出口であり、デフォルトではありません。
2. 一時切り替え儀式
この儀式の不変条件は、儀式開始時の保護状態が儀式終了時の保護状態と等しいことです。緩和はマージウィンドウの内側にのみ存在します。
手動の形式:
# 1. Open the PR, push branch, wait for CI green.
gh pr create --base main --head <feature-branch> --title "..." --body "..."
gh pr checks <pr-number> # poll until all green
# 2. Relax approver-count to 0.
gh api repos/<owner>/<repo>/branches/main/protection/required_pull_request_reviews \
-X PATCH -F required_approving_review_count=0
# 3. Squash-merge with admin override (admin override now succeeds because the
# review-approval requirement is the only block enforce_admins refuses).
gh pr merge <pr-number> --squash --delete-branch --admin
# 4. Restore approver-count to 1.
gh api repos/<owner>/<repo>/branches/main/protection/required_pull_request_reviews \
-X PATCH -F required_approving_review_count=1ラップされた形式:手順 2 → 3 → 4 を単一の非対話シーケンスで実行し、手順 3 が失敗した場合に try / finally でロールバックの安全性を確保するアトミックな Python ラッパーについては scripts/dev/admin_merge.py を参照してください。
python scripts/dev/admin_merge.py <pr-number>3. 不変条件
- 切り替えウィンドウを最小化。 手順 2 → 3 → 4 は単一の非対話シーケンスで実行されます。保護が緩和状態にあるのはマージ API 呼び出しの間だけであり、観測されたすべての実行で 1 秒未満です。
- 復元は無条件。 手順 3 が失敗しても手順 4 は実行されます(ラッパーの
try/finally句があらゆる終了パスで復元を保証します)。失敗したマージ試行は保護を緩和状態のまま残しません。 - CI ゲートは全工程を通じて維持される。
required_status_checksは切り替えの影響を受けません。マージには引き続き、設定済みのステータスチェックコンテキストが緑であることが必要です。 - 監査証跡が可視。
gh pr merge --adminの注記は PR のマージメタデータに現れます。保護 API の切り替えはリポジトリの監査ログにメンテナーのアイデンティティとして現れます。
4. 儀式を使うべきでない場合
この儀式はメンテナー自身の PR の自己マージのためのものです。同僚レビュアー(書き込みアクセスを持つコントリビューター)が利用可能な場合は、標準の gh pr review --approve + gh pr merge --squash --delete-branch パスが正しく、儀式は不要です。
リポジトリが 2 人目のメンテナーをオンボーディングする場合は、儀式を引退させてください。標準の PR レビューフローが、いかなる切り替えもなしに保護が意図する人間によるレビューを復元します。
5. 過去の実行の監査
儀式の各呼び出しは、オペレーターが事後に検査できる 3 つの永続的な表面に残ります。
- PR のマージメタデータ。
gh pr view <pr-number> --json mergedBy,mergedAt,mergeCommitはマージのアクター、マージのタイムスタンプ、squash コミットの SHA を返します。mergedByのアクターは手順 3 を実行したメンテナーのアイデンティティと一致します。 - リポジトリの監査ログ。
gh api /repos/<owner>/<repo>/actions/runs --jq '.workflow_runs[] | select(.event=="push")'はマージ後の CI 実行をマージコミットと関連付けます。リポジトリの保護ルール監査ログ(Settings → Branches の UI を介したgh api /repos/<owner>/<repo>/branches/main/protectionの履歴)は、マージウィンドウを挟む手順 2 と手順 4 の切り替えを示します。 - メンテナーの git 履歴。
git log main --merges --first-parentはmain上のすべての squash マージコミットを、コミットの件名に対応する PR 番号(gh pr merge --squashのデフォルトに従った(#<pr-number>)サフィックス)とともに一覧表示し、オペレーターが過去のあらゆるランディングをその PR と儀式の呼び出しまで遡れるようにします。
scripts/dev/admin_merge.py がエントリーポイントである場合、その非対話ログ(stdout)は 3 つの保護 API 呼び出しとマージ API 呼び出しを順番に名前付きで出力します。ラッパーを tee にパイプすれば、GitHub 側のログ保持に依存することなく実行ごとの監査フラグメントを取得できます。