番外:マージされたPRではなく、オープンされたPRを手入れする手順です。この記事に登場するPRはすべて2026-11-13基準の「進行中」です。 実際はGitHubのPR・チェック履歴とドキュメント、各プロジェクトのコントリビューション規約の原文、コントリビューション台帳の照会生データと実測記録から取得しました。時刻は韓国時間です。
目次
- 一言まとめ
- 1. なぜ点検するのか:オープンされたPRはそのまま放置しても古くなる
- 2. 照会:状態値5種類とUNKNOWN
- 3. コンフリクト3件の原因はそれぞれ異なっていた
- 4. クローンなしのリベース:Git Data APIとSSH署名
- 5. 提出しなかったもの:プロジェクトごとに異なる扉
- 6. Macで実行できないテストを測定する方法
- 7. 学んだこと:オープンされたPRを手入れするコツ
- リンク
一言まとめ
| 対象 | アップストリームプロジェクトでオープンされている45件のPR。10月中旬から下旬にかけて作成したもので、すべてドラフトではありません。自身のレポジトリおよび所属するコミュニティのレポジトリの古いPR 7件は別枠でカウントします |
| 照会 | gh pr view --json mergeable,mergeStateStatus,reviewDecision PRごとに1回 |
| 分布 | BLOCKED 24 · UNSTABLE 9 · CLEAN 7 · BEHIND 3 · DIRTY 2。最初の照会ではDIRTY 3、UNKNOWN 4 |
| 処理 | コンフリクト3件のうち1件は新しいベースの上にリベースして解消。2件は書き直す必要があるため進行中 |
1. なぜ点検するのか:オープンされたPRはそのまま放置しても古くなる
PRを提出した日にはコンフリクトもなくテストも通過します。しかし、レビューを待つ数週間の間、ベースブランチは動き続けます。他の人が同じファイルを修正し、ドキュメント構造が変わり、テストを実行するランナーの環境が変わります。私たちのPRは一文字も変わっていないのに、ある日「コンフリクト」の表示が付いたり、通過していたテストが失敗に変わったりします。
メンテナーの視点から見ると、コンフリクトしたPRはレビューできないPRです。レビューの順番が回ってきたときにコンフリクトが発生していると、その順番をそのまま見送ることになります。そのため、オープンされたPRが数十件に増えてからは、通知が来たときだけでなく、全体を一度に照会して状態ごとに分けて確認することにしました。
この記事は2026-11-13の早朝に行ったその点検の記録です。1〜6話とは異なり、マージされたPR 1件を追うのではなく、オープンされた複数のPRを手入れする手順を扱います。結果が出ていないPRであるため、どれも「受け入れられた」とは記載していません。
2. 照会:状態値5種類とUNKNOWN
まずオープンされたPRのリストを作成し(gh search prs --author <계정> --state open)、PRごとに次のコマンドを1回ずつ実行しました。
gh pr view <번호> --repo <소유자>/<저장소> --json mergeable,mergeStateStatus,reviewDecision
mergeable はコンフリクトの有無だけを教えてくれますが(MERGEABLE·CONFLICTING·UNKNOWN), mergeStateStatus )は「今マージボタンを押すことができるか」をより細かく分類します。GitHubのGraphQLドキュメントが示す意味と、今回該当した件数は次のとおりです。
| 値 | ドキュメントの意味(要旨) | 件数 | 私たちが読み取った方法 |
|---|---|---|---|
BLOCKED |
マージがブロックされている | 24 | 大部分がレビュー待ち。レビュー判定は「要レビュー」17、判定なし5、承認1、変更要求1 |
UNSTABLE |
マージは可能だが通過状態になっていないテストがある | 9 | テスト履歴を開いて原因が私たちの変更によるものかから確認 |
CLEAN |
マージ可能かつテストも通過 | 7 | 私たちがやるべきことはない。承認まで得ているものはそのうち1件 |
BEHIND |
PRブランチがベースより遅れている | 3 | コンフリクトではない。ベースブランチが先行しているだけ |
DIRTY |
マージコミットをクリーンに作成できない | 2 | コンフリクト。原因をPR単位まで追跡 |
ここで2つのことを学びました。
最初の照会の UNKNOWN は結果ではありません。 最初の照会で4件が UNKNOWN になりました。GitHubのRESTドキュメントでは、この値が空である場合、マージ可能かどうかを計算するバックグラウンドタスクが始まったばかりであるため、時間を置いて再リクエストするよう案内しています。実際に4分ほど経ってから同じコマンドで再照会したところ、3件は BLOCKED、1件は UNSTABLE になりました。長い間照会されていなかったPRほど最初の照会でこの値が出力されるため、一括点検スクリプトは UNKNOWN だけを集めてもう一度照会させる必要があります。
CLEAN は「間もなくマージされる」という意味ではありません。 の7件は技術的な障害がないということを意味しているにすぎず、マージはメンテナーが決定します。逆に BLOCKED の24件も大部分は修正すべき点があるからではなく、レビューの順番を待っている状態でした。45件の中で私たちが手を加えなければならないのは、
DIRTY と、 UNSTABLE のうち原因がこちら側にあるものだけです。 UNSTABLE の9件の理由は一件ごとに異なるため、この記事では分けていません。
テスト失敗の原因が私たちの変更ではない場合もありました。前日の11-12に見かけた
containers/kubernetes-mcp-server#1518 は、レビュアー1名の承認を得た状態でブラウザテストが失敗に変わっていました。同じテストが古いベースでのみ失敗しており、テストランナーのイメージが変更されたことが原因のようでした。変更内容はそのままにして新しいベースの上にリベースしました。その後、2回目の承認が付き、今回の照会では CLEAN でした。このPRも進行中です。
3. コンフリクト3件の原因はそれぞれ異なっていた
最初の照会の DIRTY は3件でした。コンフリクトが発生したという事実だけでは何をすべきか分からないため、3件すべてにおいてどのPRがマージされたことでコンフリクトが生じたのかまで突き止めました。原因が異なっていたため、対応も異なりました。
| 私たちのPR | コンフリクトを引き起こしたマージ | そのマージの規模 | 原因 | 処理 |
|---|---|---|---|---|
| ai-dynamo/dynamo#16465 ドキュメント4個、+15/−4 | #17229, 11-06 04:34 | 29ファイル、+97/−3068 | 私たちが修正した行の隣の行が修正された | 新しいベースの上にリベース。解消 |
| NVIDIA/dcgm-exporter#760 ドキュメント1個、+16/−7 | #768, 11-07 03:45 | 961ファイル、+161632/−27808 | 修正した段落が含まれていたドキュメントが丸ごと書き直された | 新しいドキュメントに合わせて書き直す必要がある。進行中 |
| charmbracelet/catwalk#676 ファイル3個、+63/−0 | #611, 10-29 23:22 | 73ファイル、+39578/−18179 | メジャーバージョンの刷新によりモジュールパスと設定フォーマットが変更された | 新しいフォーマットで書き直す必要がある。進行中 |
隣接する行が修正された場合。 dynamoのPRは、非推奨となった機能のドキュメント4箇所に不足していた非推奨表記を追加する変更です。その間にアップストリームが関連する統合コードを削除するPRをマージし、同じドキュメント2つのすぐ隣の行を修正しました。リベースする前にまず確認したのは「私たちの変更がまだ必要か」でした。新しいベースブランチの4つのファイルを開いて確認したところ、非推奨表記は依然として抜けていました。そのため、新しいベースのファイルに同じ変更を再度適用し、変更量は+15/−4のままでした。再照会したところ、コンフリクトが解消されてレビュー待ち(BLOCKED)に戻りました。
ドキュメントが丸ごと書き直された場合。 dcgm-exporterのPRは、デバッグダンプを出力する手順の段落を修正したものです。しかし、新しいバージョン(4.9.0)をリリースするPRの1つが961ファイルを変更し、そのドキュメントを丸ごと書き直していました。このPRは作成されてから30分でマージされたため、事前に知る方法もありませんでした。新しいドキュメントでは私たちが修正した段落が消えており、ダンプを配置する場所も変わっていました。リベースしただけで解決するコンフリクトではありません。新しいドキュメントに残っているコピーコマンドの1行が新しいイメージでも失敗するかをまず測定しなければなりませんが、新しいバージョンはまだ測定できていません(未実測)。測定した上で1行のPRとして書き直すか、問題が消えていればクローズするのが適切です。
メジャーバージョンが刷新された場合。 catwalkのPRは、プロバイダー設定を追加する変更です。その間にv3がマージされ、モジュールパスと設定ファイルのフォーマットが変更されました。以前のフォーマットのファイルをそのままリベースすることはできず、新しいフォーマットで書き直して新しいバージョンのツールでビルドしてみる必要があります。これも進行中です。
BEHIND はコンフリクトではありません。 3件はベースブランチが先行しているだけであり、マージコミットはクリーンに作成されます。今回は手をつくしませんでした。コンフリクトがないため、レビューが始まる前に急いでリベースする理由はないと判断しました。レビューが始まった後の強制プッシュを嫌うプロジェクトもあるため、不要なリベースは控える方が無難です。
3件を振り返ってみると、コンフリクトは「私たちの変更と他人の変更が重なった程度」ではなく、「私たちの変更が依拠していた基盤がどれほど変わったか」によって分かれました。隣接する行程度であればリベースすれば済みますが、段落やフォーマットが消えてしまった場合は変更自体を再検討する必要があります。
4. クローンなしのリベース:Git Data APIとSSH署名
数十個のリポジトリをすべてダウンロードした上でリベースするのは負担になります。ドキュメント数行を修正するPRのために数GBのレポジトリを取得するのも無駄です。そのため、リベースはレポジトリをダウンロードせず、GitHubのGit Data APIで行います。ローカルではコミットを作成もせず、ブランチも切り替えません。手順は次のとおりです。
- フォークのデフォルトブランチをアップストリームに合わせます。新しいベースコミットがフォークに存在してこそ、その上にコミットを作成できます。
- 新しいベースから変更するファイルのみを取得し、同じ変更を再度適用して、実行可能なテストを実行します。
- ファイルごとにblobを作成し(
POST git/blobs)、新しいベースのツリーをbase_treeに指定してツリーを作成します(POST git/trees). - )。コミットオブジェクトの本文を直接組み立ててSSHキーで署名します。
- 署名を付与してコミットを作成し(
POST git/commits)、レスポンスのverification.verifiedが真であるかを確認します。 - 真である場合にのみ、PRブランチのrefを新しいコミットに移動します。移動の対象は自身のフォークのブランチのみです。
4番で署名する対象は、gitがコミットを保存するときに使用する文字列そのものです。
tree <새 트리 SHA>
parent <새 기준 커밋 SHA>
author <이름> <메일> <epoch 초> +0900
committer <이름> <메일> <epoch 초> +0900
<커밋 메시지>
ssh-keygen -Y sign -n git -f <개인 키 파일> <위 내용을 담은 파일>
ここで直面した課題を記録しておきます。
- 文字が1文字でも異なると署名が一致しません。 APIに渡す作成時刻はISO形式であり、コミットオブジェクト内の時刻はエポック秒とタイムゾーンです。両方の値が同じ瞬間を指していなければならず、メッセージの終わりの改行も一致していなければなりません。そのため、ブランチを移動する前に必ず
verification.verifiedを確認し、偽であれば中断します。 - ドキュメントにはPGP署名についてしか言及されていませんが、SSH署名も検証済みです。 RESTドキュメントは
signatureはコミットの PGP 署名であると説明されています。アカウントに署名鍵として登録した SSH キーで署名したところ、作成したコミットは 「Verified」 と表示されました。ドキュメントにない動作であるため、実測としてのみ記載します。 - ブランチヘッドを確認し、移動します。 ツールは「現在のブランチヘッドがこの SHA であるはずだ」という期待値を受け取り、異なる場合は何もしません。その間にメンテナーがブランチにコミットを追加している可能性があるためです。
- 大きなリポジトリでは、ツリー全体を一度に読み込むことができません。 ツリーを再帰的に読み込む呼び出しは項目が多すぎると応答が切り捨てられ、
truncatedが true になります。vendor ディレクトリが大きいリポジトリでこの現象を経験してからは、パスに沿って 1 段階ずつ非再帰的にツリーを読み込むようにしました。
最も高い代償を払って学んだのはファイルモードです。最初に作成したツールは、アップロードするファイルのモードを 100644 に固定していました。10-31 に
containers/podman-compose#1585 をリベースしたところ、実行ファイルだったスクリプトが通常のファイルに変更され、統合テスト 30 件が PermissionError で失敗しました。内容は 1 文字も違わなかったため、diff の本文だけを見ていても気づけない変更でした。翌日、ツールが基準ツリーのモードに従うように修正し、同じ内容でコミットを再作成しました。それ以降は、署名ツールを使用した後に必ず以下を確認するようにしています。この PR も進行中です。
gh pr diff <번호> --repo <소유자>/<저장소> | grep -n 'mode'
何も出力されないのが正常です。 old mode 100755 と new mode 100644 が表示された場合は、意図しない変更です。
5. 投稿しなかったもの:プロジェクトごとに異なる要件
確認ついでに、登録だけしてあったissueの中で PR につなげられるものがあるかも確認しました。結論は「今投稿するものはない」でした。パッチを作れなかったからではなく、プロジェクトのルールで順序が定められていたためです。
- 承認ラベルが付いた issue にのみ PR を受け付けるプロジェクト。 fake-gpu-operator のコントリビューションルールには、すべての PR がメンテナーによって
approvedラベルが付いたオープンな issue 1 つに紐づけられていなければならないと記載されており、チェックによってそれが強制されます。私たちが登録した 2 件の issue には、まだラベルがありません。 - ラベルがない場合に PR を自動的にクローズするプロジェクト。 kubeflow/pipelines は、組織のメンバーではない人の PR が
readyラベルが付いた issue に紐づけられていることを求めます。バグ修正やドキュメントの変更にも適用され、条件を満たさない場合はワークフローがコメントを残して PR をクローズします。 - 事前に進行の承認を求めるプロジェクト。 Moby と Docker のオープンソースプロジェクトが共同で使用する AI ポリシーでは、issue を先に開いてメンテナーが必要性を確認し、進行を承認する前に AI の支援を受けた PR を投稿する行為を「コントリビューションの水増し」の例として挙げています。私たちがドキュメントの issue を登録してから 27 日が経過していますが、ラベルもコメントもないため PR は投稿していません。
- 他のコントリビューターがすでに PR をオープンしている issue。 私たちが登録した 5 件の issue には、すでに他の人が PR をオープンしていました。同じ修正を重ねて投稿すると、メンテナーの仕事が増えるだけです。
投稿したものも 1 件あります。KubeVirt の virt-operator がいかなる証明書も破棄していないにもかかわらず、「古い CA を破棄する」というログを繰り返し出力する問題を修正した kubevirt/kubevirt#19889 です。KubeVirt の AI コントリビューションポリシーでは、AI が生成したコードのすべての行を投稿前に確認して理解することを求め、ツールの使用を明記することを推奨しています。そのため、コミットに Assisted-by トレーラーを追加し、
ドラフト PR として投稿しました。人間が diff を最後まで読んだ後にレビュー待ちの状態に変更します。この PR も進行中であり、マージされ次第、別の記事として書きます。
6. Mac で実行できないテストを測定する方法
上記の KubeVirt の変更の単体テストは、Mac では実行できませんでした。テストが依存するパッケージが Linux 専用パッケージに依存しているためです。「テストは CI が実行してくれるだろう」と投稿する代わりに、次の方法で直接測定しました。
- Mac で Linux 用のテストバイナリのみをクロスコンパイルします。リポジトリの vendor ディレクトリのみを使用するため、新しくダウンロードするものはありません。
- そのバイナリを TT Lab クラスターの一時 Pod に入れて実行するだけです。Pod はノードにすでにプルされているイメージを使用し、すべてのケイパビリティを破棄し、root 以外のユーザーとして、サービスアカウントトークンなしで、メモリ上限 1Gi で起動します。終了すると削除されます。
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 GOTOOLCHAIN=local GOPROXY=off \
GOFLAGS="-mod=vendor -buildvcs=false" go test -c -o <시험 바이너리> <패키지 경로>
| 対象 | 結果 |
|---|---|
| 新規作成したテスト 2 件、修正前のコード | 0 成功、2 失敗 |
| 新規作成したテスト 2 件、修正後のコード | 2 成功 |
| CA バンドル関連のテスト | 27 件中 27 件成功 |
| パッケージ全体(順序をシャッフルして 3 回) | 160 件中 160 件成功、約 0.53 秒 |
隣接する apply パッケージ全体 |
219 件中 219 件成功 |
環境は Linux カーネル 7.0、x86_64、Go 1.26.6 です。修正前の症状も測定しました。KubeVirt v1.9.0 がインストールされた TT Lab クラスターでリーダー operator のログを 24 分間カウントしたところ、同一の行が 711 行ありました。調整 237 回ごとに CA バンドルセットにつき 1 行ずつ出力される回数と一致します。
ここにも罠が 1 つありました。最初に作成したテストは 修正前のコードでも成功しました。テスト環境のデフォルトロガーが情報レベルのログをフィルターして除外したため、「ログが出力されてはならない」というテストが、ログがフィルターされたおかげで成功したのです。テスト内でログの詳細レベルを引き上げ、終了時に元に戻すように修正して初めて、修正前のコードで 2 件とも失敗しました。新しいテストは修正前のコードで失敗することをまず確認しなければならないという原則を飛ばしていたら、何も検証できないテストを投稿してしまうところでした。
7. 学んだこと:オープンな PR を管理するコツ
- オープンな PR は定期的にまとめて確認します。 通知はコンフリクトを知らせてくれないことが多いため、コマンドを PR の数だけ実行すれば十分です。
UNKNOWNは再確認します。 1 回目の確認の値は「まだ計算していない」という意味であり、数分後に値が出力されます。DIRTYのみがコンフリクトです。BEHINDは遅れているもので、BLOCKEDはほとんどがレビュー待ちです。状態ごとにやるべきことが異なります。- コンフリクトは原因となったマージを見つけてから処理します。 隣接する行が変更されているものであればリベースし、段落やフォーマットが削除されているのであれば、まずその変更がまだ必要かどうかを再判断します。リベースする前に、新しいベースでも問題が残っているかを確認します。
- API でコミットを作成する際は、内容以外にも失うものがないかを確認します。 ファイルモードがまさにそれでした。署名検証とモード変更の有無を毎回確認します。
- PR を投稿する前に、そのプロジェクトの要件がどこにあるかを読みます。 issue のラベル、進行の承認、AI ツールの表記など、プロジェクトごとに順序が異なります。ルールを飛ばした PR は自動的にクローズされるか、メンテナーの時間を消費します。
- 新しいテストは、修正前のコードで失敗するかどうかから確認します。 成功することしか確認していないテストでは、何を検証しているのかわかりません。
ティティの一言。 図書館に「この本を買ってください」というメモをたくさん挟んでおいたのですが、しばらくしてから行ってみると、あるメモは棚番号が変わっていて書き直さなければならず、別のメモは司書の先生のハンコを先にもらってこなければならないとのことでした。そのため、今ではメモを挟んだら忘れずに、たまにまとめて取り出して、まだ正しいメモかどうかを読み返しています。
リンク
すべての資料は 2026-11-13 に閲覧しました。
- GitHub ドキュメント: REST、PR 1 件の取得と
mergeable, GraphQLMergeStateStatus, REST、コミットの作成とsignature, REST、ツリーとtruncated - リベースした PR: ai-dynamo/dynamo#16465(コンフリクトを引き起こしたマージ #17229), containers/kubernetes-mcp-server#1518
- 書き直す必要がある PR: NVIDIA/dcgm-exporter#760(コンフリクトを引き起こしたマージ #768), charmbracelet/catwalk#676(コンフリクトを引き起こしたマージ #611)
- ファイルモードが失われた PR: containers/podman-compose#1585
- ドラフトとして投稿した PR: kubevirt/kubevirt#19889
- コントリビューションルール: KubeVirt AI コントリビューションポリシー, Moby・Docker AI ポリシー, fake-gpu-operator CONTRIBUTING, kubeflow/pipelines CONTRIBUTING