git-release-finish について
git-release-finish は、リリースブランチをメイン開発ブランチへマージするチーム向けの Git リリース完了スキルです。リモート状態、タグ、マージ制約、競合、余分なファイルを段階的に確認し、マージとレポート作成を行います。
主な機能
- 01競合マーカーなどの残留物をスキャン
- 02タグ規則の分析とリモートコミットの確認
- 03メインブランチとマージ制約の特定
- 04MR または PR の作成と競合確認
- 05必要に応じたブランチ内の余分なファイル整理
- 06マージとリリースレポートの作成
活用シーン
- 01リリースブランチからメインブランチへのマージ完了
- 02複数リポジトリにまたがるリリースの調整
- 03マージ前の競合とブランチ残留物の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ fudesign2008/ open-skills --skill git-release-finishインストール後、Claude Code で/git-release-finishを入力して読み込まれたか確認します。
同梱ファイルreferences/cross-release-sync.mdreferences/ff-cherry-pick.md
詳細
Git リリースの完了
前提スキルの確認
読み込み時に、report-output-discipline が利用可能であることを確認してください。見つからない場合は中止し、次を出力してください。
npx skills add FuDesign2008/open-skills -g --skill '*' --yes
黙って代替手段に切り替えてはいけません。
役割:イテレーション終了時のリリースワークフローです。リリースブランチにタグを付け、メインブランチに向けて MR/PR を作成し、競合を解決し、余分なファイルを整理してマージします。Git 操作はネイティブコマンドで実行し、MR/PR 操作はインテントとして表現します。実行中のエージェントは、環境に応じてプラットフォームの CLI(
glab/gh/tea/ 同等のもの)を選択します。連携:
git-release-start(イテレーション開始時にリリースブランチを作成)↔git-release-finish(このスキル)。依存関係:競合解決のフェーズ 6 は
git-conflict-resolveスキルに委任されます(セマンティック分析、複数ラウンドでの集約)。リモート優先の原則:ローカルで何かを実行する前に、リモートの状態を確認してください。タグ付けの前に
git fetchを実行し、リモートのコミット SHA を検証します。検証していないローカル HEAD にタグを付けてはいけません。リモートより遅れている可能性があります。プラットフォームが対応している場合は、リモート API によるタグ作成を優先してください(ローカル状態とのずれを防げます)。それ以外の場合は、検証済みのリモート SHA に基づいてローカルでタグを作成します。
複数リポジトリでの規律
単一リポジトリでの実行では、このセクションを省略できます。複数リポジトリ(2 つ以上)で実行する場合は、3 つすべてのルールに従ってください。それぞれ、実際の事後検証から得られたものです。
M.1 — 作業ディレクトリの明示的な分離。 ツール呼び出し層はリポジトリのコンテキストを保持しません。リポジトリ A に cd した後、次の Bash 呼び出しで cd しなければ、引き続き A で実行されます。複数リポジトリを並行してリリースする際に cd を明示しないと、誤ったリポジトリに MR を作成したり、誤ったブランチをマージツリーにしたり、誤った MR をマージしたりします。複数リポジトリの実行では、すでに作業対象の場所にいると確信していても、すべてのコマンドブロックを cd <REPO_PATH> && で始めてください。 各確認テーブル(フェーズ 1/2/3/3.5 の出力)には Repo 列を含めます。
M.2 — リポジトリ間での失敗パターンの伝播。 あるリポジトリで環境上の制限(ff マージモード、保護ブランチのルール、CI パイプラインの要件、CLI の認証失敗)が発生した場合、フェーズ 9 にまだ到達していないすべてのリポジトリで、直ちに同じ確認を実行してください。 これは未実施分を補うための確認であり、メインフローの重複ではありません。フェーズ 3.5 では、すでにすべてのリポジトリを並行して確認しています。
| Repo A で判明したこと | repo B(および、まだマージされていないすべてのリポジトリ)で実行すること |
|---|---|
merge_method = ff | フェーズ 3.5 のフィンガープリントを実行し、チェリーピック戦略を想定する |
| リリースブランチが保護されている(push=No one) | B のリリースブランチ保護を確認し、保護ブランチ用の代替手順を事前に計画する |
| CLI の認証失敗 | プラットフォームの auth status を再実行する |
| パイプラインの失敗によりマージがブロックされる | B の CI 設定とパイプラインの状態を確認する |
M.3 — 並行処理と逐次処理の境界。 リポジトリに依存しない操作(タグ付け、MR/PR 作成、競合のドライラン)は並行して実行します。競合解決(フェーズ 6)は厳密に逐次実行し、一度に競合状態にできるのは 1 つのリポジトリのワークツリーだけです。
使用する場合/使用しない場合
使用する場合(単一または複数リポジトリ):タグの命名規則が曖昧、または変更されてきた場合。メインブランチの名前が不明な場合(master / main / develop はリポジトリによって異なります)。リリースブランチからメインブランチへのマージでコードの競合が発生する可能性がある場合。MR/PR ブランチに不要なファイル(AI ツール設定、テンポラリスクリプトなど)が含まれている可能性がある場合。構造化されたリリースレポートが必要な場合。
使用しない場合:Git 以外のホスティング(SVN、Mercurial など)。
このフローは最初から最後まで実行します。リポジトリの数やリリースがどれほど「簡単」に見えるかにかかわらず、すべてのフェーズが必須です。「今回は簡単そう」という判断は、事前確認を省略し、不要なファイルをメインブランチに混入させる最も一般的な原因です。
前提条件:プラットフォームの CLI がインストールされ、認証済みであること(開始前に認証を確認してください)。各リポジトリのリモートにアクセスできること。バージョン番号(例:8.2.60)をユーザーに確認済みであること。
フェーズマップ
| フェーズ | 操作 | 主なツール |
|---|---|---|
| 0 | 事前ヘルスチェック(残存マーカーのスキャン) | git grep + git log -S |
| 1 | タグの命名規則を分析 | git tag |
| 2 | タグを作成してプッシュ(リモートを先に処理) | git fetch / git tag / git push / リモート API |
| 3 | メイン開発ブランチを特定 | git remote show + git log |
| 3.5 | 環境フィンガープリント(merge_method、保護設定、ff の検出) | プラットフォーム API + git merge-base --is-ancestor |
| 4 | MR/PR を作成 | プラットフォーム CLI |
| 5 | コンフリクトのドライラン | git merge-tree |
| 5.5 | MR のマージ可能性を検証(merge-tree=0 の後に必須) | プラットフォーム CLI/API |
| 6 | コンフリクトを解決(存在する場合) | git-conflict-resolve スキル |
| 7 | MR/PR ブランチの余分なファイルを整理(マージモードのみ) | git ls-tree + comm |
| 8 | 残存マーカーゲート(無条件) | git grep |
| 9 | MR/PR をマージ | プラットフォーム CLI |
| 10 | レポートを出力 | — |
フェーズ 0、3.5、8 は無条件で実行されます。どの経路でも実行されます。フェーズ 0 は事前チェック、3.5 はマージ制約ゲート、8 は最終マージ前ゲートです。いずれもフェーズ 5 でコンフリクトが検出されたかどうかには依存しません。
実行経路
| 状況 | 経路 |
|---|---|
| merge-tree=0 で、MR がマージ可能 | 0→1→2→3→3.5→4→5→5.5→8→9→10(6/7 をスキップ) |
| merge-tree=0 だが、MR がマージ可能でない | 0→1→2→3→3.5→4→5→5.5→6(rebase)→8→9→10(7 をスキップ) |
| コンフリクト > 0、ソースが 3 コミット以下かつコンフリクトのあるファイルが 1 つ以下 | 0→1→2→3→3.5→4→5→6(rebase)→8→9→10(7 をスキップ) |
| コンフリクト > 0、その他すべてのケース | 0→1→2→3→3.5→4→5→6(merge)→7→8→9→10 |
| フェーズ 3.5 で ff モード + non-fast-forward を検出 | 0→1→2→3→3.5(cherry-pick)→8→9→10(4 のデフォルト MR / 5 / 5.5 / 6 / 7 をスキップ。ff cherry-pick は sync-release MR を作成します。references/ff-cherry-pick.md を参照してください) |
フェーズ 3.5 は、すべての経路で必須の通過点です。その判定によって、以降のフローでデフォルトのマージ/リベースブランチ(経路 1~4)を使用するか、ff cherry-pick ブランチ(経路 5)を使用するかが決まります。
フェーズ 0:事前ヘルスチェック(残存マーカーのスキャン)
無条件で実施します。 フェーズ 1 の前に、すべてのリポジトリで実行してください。作業ツリーまたは最近のコミットに競合マーカーがすでに含まれている場合は、リリース前に除去してください。そうしないと、残存物がマージによって main に入り込みます。
この手順が必要な理由:フェーズ 5 の git merge-tree が検出するのは、今後のマージで新たに発生する競合だけです。ブランチの履歴にすでにコミットされている競合マーカー(失敗した AI 自動解決の成果物が、なおプッシュされてしまうケースでよく見られます)は検出できません。フェーズ 0 がなければ、これらのマーカーがマージに紛れ込んで main に入ってしまいます。
正確な正規表現(このスキルの以降のすべてのスキャンで再利用します):
^<{7,} |^={7,}$|^>{7,} |^\|{7,}
標準の 7 文字のマーカー、標準外の 8 文字以上のマーカー、diff3 の ||||||| base マーカーに一致します。git grep は .gitignore を自動的に適用するため、追跡されていないビルド成果物は除外されます。^={7,}$ は行末まで = だけが続く場合に一致するため、/* ====== */ のような CSS コメントには一致しません。^<{7,} は 7 個以上の < の後にスペースが続く場合に一致するため、通常の < 演算子には一致しません。
残存物の判定 — まずペアを確認します(正式なルール。このスキルおよび git-conflict-resolve のすべてのスキャンで、同じ基準を適用します):上記の正規表現は検索パターンであり、判定そのものではありません。同じファイルに開始/終了マーカー(^<{7,} /^>{7,} /^\|{7,} )も含まれている場合に限り、一致を真の残存物と見なします。= だけの行は単独では誤検出です(The MIT License の下に =============== を引いたサードパーティライセンスの見出しや、Markdown の区切り線など)。検出結果があっても、それだけで残存物とは限りません。ペアの有無で判定します。
L4a — 作業ツリーのスキャン:
git grep -lE '^<{7,} |^={7,}$|^>{7,} |^\|{7,} ' 2>/dev/null
L4a のトリアージ — 真の残存物と = のみの誤検出を区別します:
git grep -lE '^<{7,} |^={7,}$|^>{7,} |^\|{7,} ' 2>/dev/null | while read -r f; do
if git grep -qE '^<{7,} |^>{7,} |^\|{7,} ' -- "$f" 2>/dev/null; then
echo "TRUE RESIDUE: $f"
else
echo "false positive (pure '=' lines only): $f"
fi
done
L4b — マージコミット履歴のピックアックススキャン:
git log --all --merges --since="30 days ago" \
-S'^<<<<<<< ' --pickaxe-regex \
--format="COMMIT: %h %s" --name-only 2>/dev/null
| 結果 | 対応 |
|---|---|
| L4a と L4b の両方が空 | 正常です。フェーズ 1 に進みます |
| L4a のトリアージで真の残存物が見つかった、または L4b が空でない | 中止します。作業ツリーをクリーンアップ(解決するか、クリーンなコピーをチェックアウト)して、再実行してください |
L4a の一致がすべて = のみの行(</>/| マーカーなし) | 誤検出です。スキャン結果に記録し、フェーズ 1 に進みます |
「先にリリースして、後でクリーンアップ」してはいけません。main に残存物があると、誤った内容を基にビルドする下流の CI/CD に広がります。
フェーズ 1:タグの命名規則を分析する
タグの形式が統一されていると決めつけてはいけません。リポジトリごとに分析してください。
git tag --sort=-version:refname | head -30
| 過去のパターン | 命名規則 |
|---|---|
8.2.52、8.2.51 | プレーン形式:{VERSION} |
v8.2.40、v8.2.20 | v プレフィックス形式:v{VERSION} |
desktop-8.2.50、desktop-8.2.40 | 製品プレフィックス形式:desktop-{VERSION} |
mobile-7.5.720 | 製品プレフィックス形式:mobile-{VERSION} |
複数製品を扱うリポジトリ:最新 10 件だけでなく、タグ一覧全体を確認して product- プレフィックスがないか調べてください。各製品ラインでは独自のタグを管理しています。このリリースでは、現在の製品ラインにのみタグを付けます。
タグの命名規則は変更されることがあります。同じリポジトリで、過去は
v8.2.60を使用し、その後8.2.63に切り替えている場合もあります。最新のタグに従ってください。古いタグにプレフィックスが付いていたからといって、プレフィックスが必須だと決めつけてはいけません。履歴に一貫性がない場合は、このリリースで適用する命名規則をユーザーに確認してください。
出力(確認用テーブル):
| リポジトリ | リリースブランチ | タグ名 | 過去の最新例 |
|---|---|---|---|
| repo-A | release/X.Y.Z | X.Y.Z | 8.2.52 |
| repo-B | release/X.Y.Z | vX.Y.Z | v8.2.40 |
フェーズ 2 に進む前に、タグ名についてユーザーと合意してください。
フェーズ 2:タグを作成してプッシュする(リモート優先)
2.0 事前チェック(すべてのプラットフォームで必須)
リポジトリごとに並列で実行します:
git fetch origin <RELEASE_BRANCH>
REMOTE_SHA=$(git rev-parse origin/<RELEASE_BRANCH>)
echo "remote $RELEASE_BRANCH HEAD: $REMOTE_SHA"
# Fork detection: is local behind remote?
LOCAL_SHA=$(git rev-parse HEAD)
BEHIND=$(git rev-list --count HEAD..origin/<RELEASE_BRANCH> 2>/dev/null || echo 0)
[ "$BEHIND" -gt 0 ] && echo "Local is $BEHIND behind remote; tag will anchor to remote SHA, not local HEAD"
# Tag-already-exists check
git ls-remote --tags origin | grep -q "refs/tags/<TAG_NAME>$" \
&& { echo "Tag <TAG_NAME> already exists on remote; aborting"; exit 1; }
2.1 タグを作成する
タグを作成します。プラットフォームが対応している場合は、リモート API による作成を優先してください(ローカル状態とのずれを防げます)。方法にかかわらず、検証済みの $REMOTE_SHA を基準にします。
- リモート API を使う方法(利用可能な場合はこちらを優先):プラットフォームの「タグを作成」エンドポイントを
tag_name=<TAG_NAME>、ref=$REMOTE_SHA、message="Release <VERSION>"で呼び出します。タグはリモート上に直接作成されるため、git pushは不要です。(CLI のapiサブコマンドで引数が分割される場合は、「よくある間違い」を参照してください。) - ローカルで作成する方法(それ以外の場合):
git tag <TAG_NAME> $REMOTE_SHA && git push origin <TAG_NAME>。タグオブジェクトの基準はローカルの HEAD ではなく、$REMOTE_SHAです。
2.2 タグが正しいコミットを指していることを検証する
REMOTE_SHA=$(git rev-parse origin/<RELEASE_BRANCH>)
TAG_SHA=$(git ls-remote origin refs/tags/<TAG_NAME> | awk '{print $1}')
echo "tag <TAG_NAME> -> $TAG_SHA"
echo "expected -> $REMOTE_SHA"
# Annotated tags return the tag-object SHA from ls-remote; dereference with:
# git ls-remote origin "refs/tags/<TAG_NAME>^{}"
(注釈付きタグを参照先までたどった後で)TAG_SHA ≠ REMOTE_SHA の場合、タグは誤ったコミットに設定されています。削除してやり直してください(「よくある間違い」を参照)。
出力:リポジトリごとのタグ名、リモート SHA、検証済みタグ SHA、ステータスの表。
フェーズ 3:メインの開発ブランチを特定する
メインブランチは
master、main、developのいずれかで、リポジトリごとに異なる場合があります。
信頼度の高い順に、4 層の証拠を確認します。
- リモートのマージ履歴(最も信頼性が高い):
git log --oneline --merges -20 | grep -E "into '(master|main|develop)'"
過去の「Merge branch 'release/X.Y.Z' into 'master'」という記録から、マージ先がわかります。
-
リモート HEAD(
git ls-remote --symref origin HEADは最新かつ信頼できる情報です。git remote show originも最新の情報ですが、タイムアウトが頻繁に発生します。git symbolic-ref refs/remotes/origin/HEADはローカルキャッシュであり、古い可能性があります)。不一致がある場合は、最新の情報を取得するクエリを優先してください。 -
ブランチ命名の傾向とマージ済み MR の分布:
sync-release/*-to-<X>同期ブランチは、ブランチ名自体にマージ先が含まれています(git branch -a | grep sync-release/)。すべてのバージョンリリース MR が同じマージ先ブランチに入っている場合、その分布は運用上の証拠になります(プラットフォーム API では、target_branchで絞り込んだマージ済み MR を確認します)。 -
ユーザーへの確認(最後の手段):情報源が食い違う場合や履歴がない場合は、候補を列挙して質問します。
候補のブランチは、廃止された trunk ではなく、並行して稼働しているブランチである可能性があります。
masterという名前のブランチが、異なるイメージやインストール手順を使う独自の CI 設定を持ち、ブランチ名によって個別にトリガーされ、他のユーザーによる最近のコミットもある稼働中の並行ブランチで、trunk はmainということもあります。候補を廃止済みと判断したり、整理したりする前に、まずこの可能性を否定してください。独立した CI 設定や最近の第三者によるコミットがあれば、それは稼働中の並行ブランチです。稼働中の並行ブランチは整理しないでください。フェーズ 10 のレポートには、その実際の役割を記録してください。
リモート HEAD が廃止されたブランチを指していることがあります。移行後やブランチポリシーの変更後も、リモート HEAD が古いメインブランチを指したままの場合があります。リモート HEAD のブランチが他の候補より数百コミット遅れている場合、リモート HEAD は古いものと見なし、マージ履歴を信頼してユーザーに確認してください。
echo "HEAD candidate ahead of main: $(git rev-list --count origin/main..origin/<HEAD_BRANCH> 2>/dev/null)"
echo "main ahead of HEAD candidate: $(git rev-list --count origin/<HEAD_BRANCH>..origin/main 2>/dev/null)"
出力:リポジトリごとのソースブランチ、対象のメインブランチ、証拠の情報源の表。フェーズ 4 に進む前に、ユーザーと認識を合わせてください。
フェーズ 3.5:環境フィンガープリント(無条件)
無条件です。 MR/PR を作成する前に、各リポジトリのマージ制約を収集します。これにより、「merge-tree=0、
can_be_merged=trueなのに、mr mergeが 406 を返す」という典型的な落とし穴を防げます。
必要な理由:フェーズ 5 の merge-tree とフェーズ 5.5 の can_be_merged チェックで確認できるのは、コードレベルのマージ可能性だけです。プロジェクトレベルのマージ制約は確認できません。最もよくある落とし穴は、GitLab の merge_method = ff(ファストフォワードのみ)です。release が main の子孫ではない(release のブランチ作成後に main が更新されている)→ git merge-tree は競合 0 件を報告 → MR は can_be_merged=true(マージ結果が存在することを示すだけで、merge_method の制約は検証しません)→ ff モードではマージコミットを作成できず、release を main にファストフォワードできないため、mr merge は 406 "Branch cannot be merged" を返します。この落とし穴はフェーズ 5/5.5 では検出できず、フェーズ 9 になって初めて表面化します。フェーズ 3.5 では、この問題を事前に検出します。
チェック項目(リポジトリごとに並列実行):
- merge_method/マージコミットの許可設定 — プラットフォームの API を使って、プロジェクトの
merge_method(GitLab)またはallow_merge_commit/allow_squash_merge/allow_rebase_merge(GitHub)を照会し、ff のみのモードを検出します。 - release ブランチの保護ルール —
release/*と*のprotected_branchesを照会し、push_access_levelsとmerge_access_levelsを記録して、push=No one を検出します。git-branch-protectが利用可能な場合は、glabまたはghを通じて読み取り専用のcheck-trunkも実行します。「push denied, maintainer merge allowed」と報告します。set-trunkは実行しないでください。スキルが利用できない場合は、このフィンガープリントを保持して続行します。 - CI ゲート —
only_allow_merge_if_pipeline_succeeds(GitLab)または同等の設定を照会します。 - 子孫チェック(ff モードの判定):
git merge-base --is-ancestor origin/<TARGET_BRANCH> origin/<RELEASE_BRANCH> \
&& echo "Descendant: yes (ff possible, default flow)" \
|| echo "Descendant: no (non-fast-forward; ff-mode repos must use cherry-pick)"
戦略表:
| merge_method | release が target の子孫か? | 戦略 |
|---|---|---|
merge / rebase_merge | いずれでも | デフォルトのマージ戦略(フェーズ 6 のマージモード) |
squash | いずれでも | フェーズ 9 で --squash=false を省略します(プロジェクトで squash が強制されています)。release 上の N 個のコミットは main 上で 1 個の squash コミットになります。この点をフェーズ 10 のレポートに記録します |
ff | ✅ はい(ファストフォワード可能) | デフォルトのフロー。mr merge は直接成功します |
ff | ❌ いいえ(ファストフォワード不可) | チェリーピック戦略 — references/ff-cherry-pick.md を参照 |
ff とファストフォワード不可の組み合わせは、最もよくある落とし穴です。release ブランチは通常、main から分岐しています(両方に新しいコミットがあるため)。そのため、ff モードのリポジトリでは、デフォルトでチェリーピックを使用します。フェーズ 9 で 406 が返るまで待たないでください。
GitHub で同様に起きる落とし穴:GitHub には merge_method=ff の切り替え設定はなく、代わりに 3 つのブール値が公開されています。allow_merge_commit=false && allow_squash_merge=false && allow_rebase_merge=true(rebase のみ)の場合、PR のマージでコミットが書き換えられ、ハッシュが変わります。これは実質的に ff のみと同じです。この設定での release → main の PR は ff モードと同様に扱い、release 間の同期では references/cross-release-sync.md の rebase/ハッシュ変更の手順に従います。
出力:リポジトリごとの環境フィンガープリント表(merge_method、release の保護ルール、CI ゲート、選択した戦略)。ff モードまたは保護ルールに異常が見られる場合は、フェーズ 4 の前にユーザーと方針をすり合わせます。
保護ブランチと non-ff の組み合わせ:release が保護されている場合(push=No one)、
merge_methodにかかわらず、フェーズ 6 の rebase モードでの force push は失敗します。戦略の列に「protected-branch fallback」と記載し、フェーズ 6 でフォールバックの手順を実行できるようにします。
フェーズ 4:MR/PR の作成
リポジトリごとに並行して実行します。各プラットフォームの CLI を使って、<RELEASE_BRANCH> から <MAIN_BRANCH> へのマージリクエストを作成します(エージェントは環境に応じて glab / gh / tea を選択します)。タイトルは Release <VERSION> にします。ソースブランチは保持します(--remove-source-branch=false / --delete-branch=false)。各 MR/PR の ID と URL を記録します。
フェーズ 3.5 で ff cherry-pick 戦略が選択された場合は、このフェーズをスキップし、代わりに
references/ff-cherry-pick.mdに従ってください。MR のソースブランチは元のリリースブランチではなく、sync-release/<VERSION>-to-<TARGET>になります。
フェーズ 5:競合の検出(ドライラン、ファイルの変更なし)
リポジトリごとに並行して実行します:
BASE=$(git merge-base origin/<RELEASE_BRANCH> origin/<MAIN_BRANCH>)
# Content conflict count
git merge-tree $BASE origin/<RELEASE_BRANCH> origin/<MAIN_BRANCH> 2>&1 \
| grep -c "^changed in both"
# Branch divergence
echo "release ahead: $(git rev-list --count origin/<MAIN_BRANCH>..origin/<RELEASE_BRANCH>)"
echo "main ahead: $(git rev-list --count origin/<RELEASE_BRANCH>..origin/<MAIN_BRANCH>)"
git merge-treeが検出するのはテキスト内容の競合のみです。rename/rename 競合(ビルド成果物のハッシュ変更でよく発生します)は検出されません。競合が 0 件でも、リポジトリにビルド成果物がある場合はフェーズ 5.5 を実行してください。
結果の分類:
| 内容の競合 | ソースコミット数 | 戦略 |
|---|---|---|
| 0 | 任意 | MR 作成後にマージ可能か確認(フェーズ 5.5)し、マージ可能な場合のみフェーズ 9 に進みます |
| 0(ただし MR は実際にはマージ不可) | 任意 | リベース戦略(フェーズ 6) |
| > 0 | ≤ 3 かつ競合ファイルが ≤ 1 件 | リベース可能(コミットごとの解決にかかるコストが許容範囲) |
| > 0 | その他すべて | マージ戦略(フェーズ 6) |
merge-tree=0 の落とし穴:テキストのマージがクリーンでも、MR がマージ可能とは限りません。構造上の差異により、プラットフォームがマージを拒否することがあります。フェーズ 5.5 は必須です。
リベースの落とし穴:リベースでは、競合があるすべてのコミットで停止します。80 コミットのブランチでは、何十回も作業が中断されます。ソースが非常に短く(≤ 3 コミット)、競合が最小限でない限り、マージを優先してください。
フェーズ 5.5:MR がマージ可能か確認する
merge-tree が 0 件と報告した場合は必須です。merge-tree=0 ≠ MR がマージ可能。
プラットフォームの CLI/API で、MR の mergeable / can_be_merged の状態を確認します。その後、表を上から順に参照してください。最初に一致した行を適用します(各行は相互排他的です)。
| 結果 | 対応 |
|---|---|
can_be_merged / MERGEABLE で、かつフェーズ 3.5 で ff の落とし穴が指摘されていない | フェーズ 9 に進みます |
マージ不可(conflict / UNMERGEABLE) | リベース戦略(フェーズ 6)に切り替えます。フェーズ 9 には進まないでください |
can_be_merged だが、フェーズ 3.5 で ff モード+非 fast-forward が指摘された | フェーズ 9 の mr merge は引き続き 406 を返します。 references/ff-cherry-pick.md の cherry-pick 戦略に切り替えます。フェーズ 9 には進まないでください |
checking のまま(下記の停滞解消手順を参照) | プラットフォームでの再計算がまだ完了していません。停滞解消手順を実行し、マージ不可とは判定しないでください |
can_be_mergedが意味するのは、「コードに競合がなく、マージ結果が存在する」ということだけです。merge_method=ffやその他のプロジェクトレベルの制約を回避するものではありません。仕組みの全容と決定的な判定は、フェーズ 3.5 で確認してください。
停滞した checking 状態を解消する
複数リポジトリを実行する場合に最もよく発生します。直前の MR がターゲット参照を更新したため、プラットフォームの非同期再チェックがその後ろに滞留しています。has_conflicts=false かつ state=opened の状態で、merge_status が連続 N 回のポーリング(デフォルト N=6、間隔 4 秒)にわたって checking のままの場合:
- プラットフォームのマージ可能性再計算エンドポイント(GitLab:
GET /projects/:id/merge_requests/:iid/merge_ref)を呼び出し、再計算を強制します。 - 約 3 秒待ってから、
merge_statusを再確認します。 can_be_mergedに変わった場合は、上記の表に従って続行します。- それでも
checkingの場合は、ポーリングを続けます。再計算と待機を数回繰り返しても変化がなければ、プラットフォームの異常として扱い、ユーザーに報告します。
checking の停滞は計算が未完了であることを示しており、マージの判定ではありません。「マージ不可」を意味することはありません。
フェーズ 6:競合の解決(git-conflict-resolve に委任)
フェーズ 5/5.5 で競合が示された場合は、git-conflict-resolve スキルに委任します。source=<RELEASE_BRANCH>、target=<MAIN_BRANCH>、version=<VERSION>、および mode=merge(デフォルト)または mode=rebase(フェーズ 5 でソースが 3 コミット以下かつ競合ファイルが 1 つ以下と判定された場合のみ)を渡します。
ビルド成果物(
dist/、resources/<bundle>/、ハッシュチャンク)は、git-conflict-resolveのビルド成果物自動ショートカットによって自動的に処理されるため、追加のパラメーターは不要です。
git-conflict-resolve が完了できない場合(ユーザーによる中止、解決不能な競合、またはリベースの中断)は、停止してください。人による対応のため、作業ツリーはそのままにして、後続のフェーズには進まないでください。
git-conflict-resolve の処理が完了してグローバルレビューのチェックリストが出力され、ユーザーが確認したら、モードに応じて分岐します。
マージモード — マージブランチをプッシュして MR を置き換えます。
git push origin merge-release/<VERSION>
# Close the conflict-stalled MR via your platform CLI.
# Create a new MR: source=merge-release/<VERSION>, target=<MAIN_BRANCH>.
リベースモード — リベース結果をリリースブランチにプッシュします(元の MR は自動更新されます)。
git push origin rebase-release/<VERSION>:<RELEASE_BRANCH> --force-with-lease
--force-with-leaseは、リモートに変更がある場合にプッシュを拒否し、他の人の作業を誤って上書きするのを防ぎます。強制プッシュの前に、ユーザーに次の内容を確認してください。「このブランチを使って作業している人はほかにいますか? 強制プッシュしてよいか確認してください。」
保護ブランチのフォールバック(強制プッシュが ! [remote rejected] または pre-receive hook declined で拒否された場合):
- リベース結果を新しいブランチにプッシュします(強制プッシュではなく通常のプッシュ):
git push origin rebase-release/<VERSION> - 元の MR をクローズします
- source を
rebase-release/<VERSION>、target を<MAIN_BRANCH>にして、新しい MR を作成します
元のリリースブランチはリベース前のコミットのままです。後続のリリース → main 同期では、ハッシュの分岐を考慮する必要があります(references/cross-release-sync.md を参照)。
リベースのスキップ監査 — リベース時に warning: skipped previously applied commit <SHA> が出力された場合:
git show <SKIPPED_SHA> --stat --oneline
git log origin/<MAIN_BRANCH> --oneline --grep="<keyword>" | head -5
diff <(git show <SKIPPED_SHA> --format=) <(git show <TARGET_SHA> --format=)
| diff の結果 | 対応 |
|---|---|
| 同一 | スキップしても安全です。対応は不要です |
| 異なる | スキップされたコミットが完全には含まれていません。<SKIPPED_SHA> を手動でチェリーピックしてください |
フェーズ 7:MR/PR に紛れ込んだ余分なファイルのクリーンアップ(マージモードのみ)
リベースモードではスキップします。リベースによって target の余分なファイルが取り込まれることはありません。これはフェーズ 6 で
merge-release/<VERSION>が作成されたマージモードにのみ適用されます。
merge-release に存在し、リリースと target の両方に存在しないファイルは、誤ってステージされたローカルファイルです(競合解決中に git add -A を実行したことが原因である場合がよくあります)。
comm -23 \
<(git ls-tree -r HEAD --name-only | sort) \
<(sort \
<(git ls-tree -r origin/<RELEASE_BRANCH> --name-only) \
<(git ls-tree -r origin/<TARGET_BRANCH> --name-only) \
| uniq)
よくある該当ファイル:AI ツールの設定(.claude/、.omc/、.codemap/、opencode.json)、一時的なデバッグファイル(ci-build-error.txt、ci-fix-log.txt)。リリースまたは target のどちらかに存在するファイルは想定どおりのため、削除しないでください。
削除して amend します。
git rm --cached -rf <EXTRA_PATH_1> <EXTRA_PATH_2> # if "pathspec not found": git add -A first, then retry
git commit --amend --no-edit --no-verify
git push origin merge-release/<VERSION> --force-with-lease
comm -23 のチェックを再実行します。件数は 0 でなければなりません。
フェーズ 8:残存マーカーのゲート(無条件)
無条件のゲートです。 フェーズ 5/6/7 の処理内容にかかわらず、これはフェーズ 9 の前に実行します。最後の防衛線です。
git-conflict-resolveの先行する防御策がすべて失敗した場合でも、フェーズ 8 で競合マーカーを含むコミットを検出します。
8.1 残存マーカーのスキャン(フェーズ 0 の正規表現を使用)
対象範囲は git diff --name-only(リポジトリ全体ではありません)です。マージによって変更されたファイルだけに、新たな残存マーカーが含まれる可能性があります。リポジトリ全体をスキャンすると、CSS の ========= コメント、ASCII アート、ライセンスファイル、Markdown の区切り線による誤検出が大量に発生します。判定はフェーズ 0 のとおり、まずマーカーの組み合わせを確認します。<、>、| マーカーを含まない = のみの行は残存マーカーではなく、誤検出です。
BASE=$(git merge-base origin/<MAIN_BRANCH> HEAD)
git diff --name-only $BASE..HEAD | while read f; do
if git grep -qE '^<{7,} |^>{7,} |^\|{7,} ' -- "$f" 2>/dev/null; then
git grep -nE '^<{7,} |^={7,}$|^>{7,} |^\|{7,} ' -- "$f" 2>/dev/null
echo "Residue in $f; merge forbidden"; exit 1
elif git grep -qE '^={7,}$' -- "$f" 2>/dev/null; then
echo "pure '=' lines in $f (no <, >, | markers) — pair-first false positive, not residue"
fi
done
ビルド成果物の競合は通常、
git-conflict-resolveのビルド成果物自動ショートカットによって自動的に処理されます(リリース側を採用し、残存マーカーはありません)。フェーズ 8 は、そのショートカットによって成果物にマーカーが残った場合の安全策です。
exit 1 の場合:フェーズ 9 に進んではいけません。フェーズ 6 に戻って修正し、フェーズ 8 を再実行してください。
8.2 条件付きの追加確認(Phase 6 を実行した場合のみ)
- 競合ファイルの差分レビュー — Phase 5 で競合ありと記録されたファイルごとに、
git diff origin/<MAIN_BRANCH>..HEAD -- <conflict_file>を実行します。確認項目:双方のインポート/require が保持されていること、双方の新しい関数/クラスが保持されていること、シンボル定義が重複していないこと、ロジック全体を囲むコメントアウトされたコードブロック(//または/* */)がないこと、予期しない削除がないこと。いずれかを満たさない場合 → ❌。 - リベースでスキップしたコミットとの同等性 — Phase 6 のリベース中にコミットをスキップした場合は、差分の同等性を再確認します(Phase 6 のコマンドを使用)。差分が空でない場合、スキップしたコミットの内容が完全には含まれていないため、手動での cherry-pick が必要です。
8.3 判定
| 判定 | 条件 | 次の手順 |
|---|---|---|
| 合格 | 8.1 で残存物がなく、かつ 8.2(実行した場合)のすべてに合格 | Phase 9 に進む |
| 不合格 | 8.1 で残存物が検出された、または 8.2 のいずれかの確認が ❌ | マージ禁止。 Phase 6 に戻り、修正後に Phase 8 を再実行 |
出力:
[Residual Scan Report]
- 8.1 marker scan: 42 files in merge scope, no residue
- 8.2 diff review (if run):
- .gitignore: 8.2.70 .omc/.sisyphus changes layered on master
- src/bridge/api/MainProcessAPI.ts: both sides' new functions retained
- 8.2 rebase skip (if run):
- skipped 7437368df: diff-equal to master 7421ca91b
Verdict: Pass — proceed to Phase 9
Phase 9:MR/PR をマージする
ユーザーの確認後、リポジトリごとに並列で実行します。
- CI ゲートの確認:MR のマージ状態を照会します。状態が
mergeable/can_be_mergedでない場合は、続行する前に「CI が通過していない可能性があります — マージを確認しますか?」と伝えます。 - マージ:使用するプラットフォームのマージコマンドを実行します。
merge_method=mergeの GitLab では、--squash=false --remove-source-branch=falseを渡します。merge_method=squashの場合は、プロジェクトで squash が強制されるため--squash=falseを省略します。GitHub では--merge(プロジェクトで必要な場合は--squash)を使用します。ソースブランチは保持します。 - 確認:マージ成功の出力(
✓ Merged!/✓ Pull request ... was merged)を確認します。その後、ターゲットブランチを fetch して調べます。
git fetch origin <MAIN_BRANCH>
git log origin/<MAIN_BRANCH> --oneline -5
最新のコミットにリリースのマージが含まれていることを確認します。
- 凍結の確認 — この Phase でのマージが成功し、Phase 2 のタグが存在する場合に、
git-branch-protectが利用可能であれば実行します。このリリースブランチが凍結されているか確認します。すでに凍結されている場合は、その報告を記録します。凍結されていない場合は、現在のルールを表示して、ユーザーにfreezeを受け入れるか確認します。受け入れられた後にのみ実行します。rollbackは呼び出さないでください。ユーザーが拒否した場合、または報告でブランチが凍結されていないと示された場合は、タグと trunk のマージを維持し、その報告を Phase 10 に記載します。スキルが利用できない場合は、この手順をスキップします。
Phase 10:レポートを出力する
$(git rev-parse --show-toplevel)/release-<VERSION>-report.md に書き込みます。レジストリの行:report-output-discipline/reference.md の 发布报告(human + 接入)。Markdown レポートを書き込んだ後、report-output-discipline に従ってその隣に release-<VERSION>-report.html を生成し、両方のパスを報告します。
レポートには以下を含めます。
- タグの概要:リポジトリ/タグ名/コミット SHA/リモートでの検証状態
- MR/PR の概要:リポジトリ/ソース → ターゲット/ID と URL/マージ状態
- 競合の詳細(ある場合):ブランチの差分(リポジトリごとの先行コミット数)、ターゲットにのみ存在するコミット(競合の原因)、競合したファイルと解決方法 — チェックリストを再生成するのではなく、
git-conflict-resolveスキルのグローバルレビュー用チェックリストを直接引用してください。クリーンアップした余分なファイルの一覧 - 手動でのフォローアップ:残存リスク、保留中の cherry-pick など
- リリース間の同期(ある場合):ソースリリース/ターゲットリリース/方法/競合の概要(
references/cross-release-sync.mdを参照)
よくある間違い
| 間違い | 結果 | 修正方法 |
|---|---|---|
| 「きれいに見える」という理由でフェーズ 0 を省略する | AI による自動解決の失敗で残った履歴上の残存物が、マージ時に main に持ち込まれます | すべてのリポジトリで、毎回必ずフェーズ 0 を実行します |
| リモートを確認せずにローカルの HEAD にタグを付ける | ローカルが遅れていると、古いコミットにタグが付いてしまいます | 必ずフェッチして $REMOTE_SHA にタグを付け、ローカルの HEAD には付けないでください |
| フェーズ 3.5 のフィンガープリント取得前に MR を作成する | ff モードのリポジトリではフェーズ 9 で 406 が発生し、ロールバックに手間がかかります | どの経路でもフェーズ 3.5 は必須です |
can_be_merged=true を「マージが成功する」という意味だと信頼する | ff モードのリポジトリでは、いずれにしても 406 が返されます | フェーズ 3.5 の判定と照合してください。フェーズ 5.5 では最初に一致した行が優先されます |
| 確認せずに共有リリースブランチへ強制プッシュする | チームメイトの作業が上書きされます | --force-with-lease の前にユーザーに確認してください |
| 50 コミットのブランチでリベースする | 競合したコミットごとにリベースが停止し、何日も中断が続きます | コミットが 3 件以下、かつ競合ファイルが 1 件以下の場合を除き、マージ戦略を使用します |
cd <REPO> && のプレフィックスなしで複数リポジトリ向けコマンドを実行する | 間違ったリポジトリに MR が作成され、間違った MR がマージされます | M.1:複数リポジトリで実行する場合、すべてのコマンドブロックは cd <REPO_PATH> && で始めます |
| 最初のリポジトリでフェーズ 9 が失敗した時点で停止する | 他のリポジトリでも同じ問題に直面します | M.2:まだマージされていないすべてのリポジトリに、失敗パターンをただちに適用します |
| 間違ったコミットにタグが付いた | リリースが間違った SHA を指します | git push origin --delete <TAG> + git tag -d <TAG> を実行し、$REMOTE_SHA を基準にフェーズ 2 をやり直します。プラットフォームの API を使ってリモートのタグを削除できる場合もあります |
プラットフォームの api が Accepts 1 arg(s), received 2 を返す | シェルが引数を分割しました | 各フィールドを引用符で囲みます:-f "key=value"。その CLI バージョンで拒否される場合は、--raw-field "key=value" を使用します。CLI のアップグレードを推奨します |
| チェリーピックで空のコミットが作成された(「previous cherry-pick is now empty」) | 対象にはすでに同等の変更があります | git cherry-pick --skip を実行します。記録を残す必要がある場合に限り、--allow-empty を使用します |
git rm --cached が「pathspec not found」を返す | ファイルはワークツリーにありますが、インデックスにはありません | git add -A でワークツリーをインデックスに同期してから、再試行します |
| リリース中に CLI の認証が期限切れになる | コマンドが認証エラーで失敗します | プラットフォームの auth login を再実行し、失敗したフェーズから再開します |
付録:pre-commit フック(任意の L5 拡張機能)
ステージ済みのすべての内容に対して、フェーズ 0/8 のマーカー正規表現を実行する、Git ネイティブのフックです。一度インストールすれば、どのスキルとも独立して機能し続けます。
cat > .git/hooks/pre-commit << 'HOOK'
#!/bin/bash
# Reject staged content with git conflict markers (Phase 0/8 pair-first verdict)
# Pure '=' lines without <, >, or | markers are false positives (license underlines,
# Markdown separators) — they do not block.
# Scan the staged blobs directly: git diff output prefixes every line (+/-/space),
# which defeats the ^ anchors of the marker regex.
git diff --cached --name-only -z | while IFS= read -r -d '' f; do
if git grep --cached -qE '^<{7,} |^>{7,} |^\|{7,} ' -- "$f" 2>/dev/null; then
echo "pre-commit: staged content contains conflict markers ($f); commit blocked"
exit 1
fi
done || exit 1
exit 0
HOOK
chmod +x .git/hooks/pre-commit
git commit --no-verifyで回避できます。L5 は拡張機能であり、ゲートではありません- プロジェクトにすでに pre-commit フックがある場合は、既存のフックにマーカーチェックを追加してください
- フェーズ 0 の正規表現を使用し、同じペア優先の判定を行います
参考資料
references/ff-cherry-pick.md— フェーズ 3.5 で ff モードと non-fast-forward が検出された場合の、チェリーピックの完全なフローです。ff プロジェクトにおけるハッシュ整合性の注意点も扱います。references/cross-release-sync.md— すでに main にマージされたrelease/Aからrelease/Bに変更を同期するための、判断ツリーとコマンドです。リベースによるハッシュ変更の落とし穴も含みます。