sync-fork について
sync-forkは、fork独自のパッチを保ちながら上流リポジトリと同期するスキルです。ブランチ分析やバックアップ、リベースと再マージを案内し、破壊的な操作の前に確認を求めます。
主な機能
- 01共有ブランチと独自ブランチの分類
- 02分岐の確認と同期計画の作成
- 03復旧用バックアップブランチの作成
- 04ローカルパッチのリベースと再マージ
- 05リモートURL表示時の認証情報保護
活用シーン
- 01上流の更新後にforkを同期
- 02同期中にローカルブランチを保持
- 03ブランチのリセット前に計画を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ shhac/ skills --skill sync-forkインストール後、Claude Code で/sync-forkを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
edge-cases/history-rewrite.mdmerge-commits-in-branches.mdorphaned-branches.mdpartial-promotion.mdupstream-reverts.mdexamples/deep-chain.mdfan-out.mdindependent-branches.mdlinear-chain.mdmulti-target.mdpromoted-branch.mdscripts/sync_fork.py詳細
フォークを同期
フォークしたリポジトリを上流のリモートと同期します。フォークの共有ブランチ(例:main)は、上流の内容にローカルのパッチを再マージしたものとして維持されます。これは「パッチ適用済み上流」モデルです。同期のたびに上流の状態にリセットし、フォーク固有のブランチをリベースしてから、再マージします。
上流に更新がない場合(つまり、upstream/main がすでに fork/main の祖先である場合)、同期は何も行いません。
使用方法
/sync-fork [<fork-remote> <upstream-remote>]
- 引数なしの場合 — リモートがちょうど2つ存在するかを自動検出します。3つ以上ある場合は、ユーザーに指定を求めます。
- 引数が1つの場合 — 指定された名前だけでは判別できません。
git remote get-url <name>でURLを取得し、ユーザーに次のように尋ねます:「<name>は<url>を指しています。これはあなたのフォークですか、それともフォーク元のリポジトリですか?」 続けて、もう一方のリモート名を尋ねます。 - 引数が2つの場合 — 1つ目はフォークのリモート、2つ目は上流のリモートです。
リモートがちょうど2つある場合に自動判定するには、どちらがフォークでどちらが上流かを推測するためのヒューリスティックを使います(例:origin という名前のリモートはフォークである可能性が高く、URLの組織名がもう一方と異なるリモートは上流である可能性が高い)。推測した内容を提示し、ユーザーに確認を求めます。
⚠️ 認証情報の安全性: リモートURLには認証情報が埋め込まれている場合があります(例:https://user:[email protected]/...)。URLをユーザーに表示する前に、必ずユーザー情報部分を伏せてください。user:token@ を ***@ に置き換えます。git remote -v または git remote get-url から取得した生の認証情報を出力してはいけません。
図の凡例
このスキルに隣接するリファレンスファイルで使用します:
─── flow (left-to-right = time)
├── X branch X forks here
└── X branch X forks here (last branch at this point)
──┘ merge point (branch merges into line above)
● notable state on the line
ヘルパースクリプト
このスキルには、ブランチ分析用の決定論的な Python ヘルパー scripts/sync_fork.py が含まれています。分類、分岐の確認、依存関係グラフの構築を行い、これらの処理で一貫した結果が得られるようにします。Python 3.9 以降が必要です(標準ライブラリのみ)。
このスキルファイルを基準にスクリプトの場所を特定します。次のように実行します:
python3 <skill-dir>/scripts/sync_fork.py <subcommand> [options]
サブコマンド:classify、divergence、graph、plan。使用方法の詳細は --help を実行してください。デフォルトの出力は簡潔なキーと値の形式(LLM 向け)です。構造化された出力にするには --json を追加します。
手順
フォークを上流と同期します。以下のフェーズを順番に実行してください。
このスキルでは段階的な情報収集を行います。以下のメインフローでは一般的なケースを扱います。特定の状況(トポロジーやエッジケース)に遭遇した場合は、その時点で該当するリファレンスファイルを読むよう案内されます。**リファレンスファイルをすべて事前に読んではいけません。**必要になった場合にのみ読んでください。
リファレンスファイルは、このスキルに隣接する2つのディレクトリにあります:
examples/— ブランチのトポロジー図とリベース戦略edge-cases/— 特殊な状況への対処
フェーズ 0:事前確認
-
前回の実行が中断されていないか確認します。
sync-fork/*に一致するブランチを探します。見つかった場合、前回の同期は中断されています。存在するバックアップブランチをユーザーに示し、バックアップから復元するか、クリーンアップして(git for-each-ref --format='%(refname:short)' 'refs/heads/sync-fork/' | xargs git branch -D)最初からやり直すかを尋ねます。 -
現在のブランチを保存します。 最後に復元できるよう、
git symbolic-ref --short HEAD(またはデタッチ状態のコミット)を記録します。 -
作業ツリーがクリーンか確認します。
git status --porcelainを実行します。未コミットの変更がある場合は、それらをスタッシュします:git stash push -m "sync-fork: uncommitted changes"最後にスタッシュをポップします。スタッシュはブランチに依存しないため、この後に行うチェックアウトやリセットの操作を経ても保持されます。
-
リモートを特定します。
git remote -vを実行し、上記の「使用方法」のルールに従って、どのリモートがフォークでどれが上流かを判定します。曖昧な点がある場合は、ユーザーに確認します。
フェーズ 1:乖離状況を評価する
-
両方のリモートをフェッチします。
-
変更不要か確認します。 共有ブランチごとに、
git merge-base --is-ancestor <upstream>/<branch> <fork>/<branch>を実行して確認します。すべての共有ブランチで upstream が fork の祖先である場合、同期は不要です。upstream に新しい変更はありません。ユーザーに伝えて終了します。 -
ブランチを分類します。 ヘルパースクリプトを実行します。
python3 <script> --fork <fork> --upstream <upstream> classify共有ブランチ、fork のみのブランチ(マージ済み、昇格済み、一部昇格済み、作業中)を特定し、簡潔な形式で報告します。
- ⚠️
fork-only-partialの項目がある場合 →edge-cases/partial-promotion.mdを読みます。 - ⚠️
fork-only-promotedの項目がある場合 →examples/promoted-branch.mdを読み、対応方法をすべて確認します。
- ⚠️
-
乖離状況を確認します。 ヘルパースクリプトを実行します。
python3 <script> --fork <fork> --upstream <upstream> divergenceブランチごとのコミット数とフラグが表示されます。
- ⚠️ いずれかのブランチで
rewrite=trueかつfork_merges_only=falseの場合 → 作業を中止し、続行する前にedge-cases/history-rewrite.mdを読みます。これは upstream で実際に履歴が書き換えられた可能性もあれば、fork のみのブランチのコミットがすでに fork の共有ブランチにマージされている、想定内の upstream 修正版との乖離である可能性もあります。 - ⚠️ いずれかのブランチで
reverts>0の場合 → 作業を中止し、続行する前にedge-cases/upstream-reverts.mdを読みます。
- ⚠️ いずれかのブランチで
-
ドライランの計画を作成します。 次を実行します。
python3 <script> --fork <fork> --upstream <upstream> plan計画の全内容をユーザーに提示します。続行する前に確認を待ちます。
フェーズ 2:すべてのバックアップを作成し、共有ブランチをリセットする
破壊的な操作を行う前に、すべてのバックアップ(リセット前とリベース前の両方)を作成します。これにより、復旧ポイントを一度にまとめて作成できます。
2a. すべてのバックアップブランチを作成する
# Pre-reset backups for shared branches
for branch in <shared_branches>; do
git branch sync-fork/pre-reset/$branch <fork>/$branch
done
# Pre-rebase backups for fork-only active branches
for branch in <fork_only_active>; do
git branch sync-fork/pre-rebase/$branch <fork>/$branch
done
これらのバックアップには、次の 3 つの役割があります。
- 問題が発生した場合の復旧ポイント
--ontoリベース用の古い参照の保存先(sync-fork/pre-rebase/<parent>の先端が古い参照になります)- 依存関係グラフ用の正しい祖先参照(共有ブランチはこれからリセットされます)
2b. 共有ブランチをリセットする
各共有ブランチについて、次の順で実行します(まずデフォルトブランチ、次にその他のブランチ)。
- ブランチをローカルでチェックアウトします。(ローカルの追跡ブランチがない場合、
git checkoutが自動的に作成します。) git reset --hard <upstream>/<branch>を実行し、upstream に合わせます。git push <fork> <branch> --force-with-leaseを実行し、fork を更新します。
フェーズ 3:fork のみのブランチをリベースする
3a. 依存関係グラフを作成する
ヘルパースクリプトを実行して依存関係グラフを作成します。共有ブランチは現在 upstream を指しているため、このスクリプトは共有ブランチの祖先関係の確認に sync-fork/pre-reset/* バックアップ参照を使用します。
python3 <script> --fork <fork> --upstream <upstream> graph \
--branches <comma-separated-fork-only-active> \
--shared <comma-separated-shared> \
--backup-prefix sync-fork/pre-reset
出力には、親マップ、トポロジカル順序、孤立ブランチ、マージ先が含まれます。
- ⚠️
orphanedが(none)以外の場合 →edge-cases/orphaned-branches.mdを読みます。 - ブランチに
parentはないもののtargetがある場合、そのブランチはリセット前の共有ブランチにすでに含まれています。孤立ブランチではなく、その共有ブランチを対象とする独立したブランチとして扱います。
3b. 必須:該当するトポロジーのリファレンスを読む
続行する前に、該当するサンプルファイルを必ず読んでください。 作成した依存関係グラフに一致するトポロジーを選び、該当ファイルを今すぐ読みます。
| 検出されたトポロジー | 読むファイル |
|---|---|
| すべてのブランチが共有ブランチを直接起点とし、ブランチ間に依存関係がない | examples/independent-branches.md |
| あるブランチが別のブランチに依存している(B は A をベースとする) | examples/linear-chain.md |
| 複数のブランチが同じ親に依存している(B と C はどちらも A をベースとする) | examples/fan-out.md |
| 3 つ以上のブランチが連鎖している(A → B → C) | examples/deep-chain.md |
| 異なる共有ブランチを起点とするブランチがある | examples/multi-target.md |
| 混在(上記の組み合わせ) | 該当するファイルをすべて読む |
fork のみのブランチ間に依存関係がある場合、続行する前に、該当するファイルを読んで --onto リベース戦略を必ず理解してください。誤ると、コミットが重複し、誤った競合が発生します。
3c. トポロジカル順にリベースする
フォークにのみ存在する各ブランチについて、親を先に、子を後に処理します(グラフ出力の order を使用します)。
-
マージコミットを確認します:
git log --merges sync-fork/pre-rebase/<parent>..<branch>- ⚠️ マージコミットが見つかった場合 → このブランチをリベースする前に
edge-cases/merge-commits-in-branches.mdを読んでください。
- ⚠️ マージコミットが見つかった場合 → このブランチをリベースする前に
-
リベースします:
- 親が共有ブランチの場合:
git rebase --empty=drop <shared-branch> <branch> - 親がフォークにのみ存在する別のブランチの場合:
git rebase --empty=drop --onto <parent> sync-fork/pre-rebase/<parent> <branch> - 親がなく、
target=<shared-branch>の場合:git rebase --empty=drop <shared-branch> <branch> --empty=dropは、上流にすでに存在するコミットを自動的に破棄します。Git < 2.26 の場合は、このフラグを省略し、確認されたらgit rebase --skipを使用してください。- リベースで競合が発生した場合は解決します。何を、なぜ解決したのかをユーザーに説明してください。
- 親が共有ブランチの場合:
-
結果が空かどうかを確認します。 すべてのコミットが破棄された場合(ブランチが親と同じコミットを指すようになった場合)、ユーザーに次のように警告します:「ブランチ
<branch>は上流に完全に取り込まれているようです。削除を検討してください。」 -
git push <fork> <branch> --force-with-leaseを実行してフォークを更新します。
フェーズ 4:共有ブランチに再マージする
フォークの共有ブランチは「上流 + ローカルパッチ」として維持されています。このフェーズでは、マージコミットを上に再適用します。これにより、fork/main = upstream/main + フォークにのみ存在する作業となります。
フォークにのみ存在するブランチがターゲットとしている各共有ブランチについて、グラフ出力の target マップを使用して、次の手順を実行します。
- 共有ブランチをローカルでチェックアウトします。
- リベース済みのフォークにのみ存在する各ブランチを、トポロジカル順(親を先に、子を後に)に
--no-ffでマージします。メッセージは次の形式を使用します:git merge --no-ff <branch> -m "sync-fork: merge <branch> into <shared-branch>"- ブランチは直前にリベースされているため、これらのマージは競合なく完了するはずです。競合が発生した場合は解決し、何を解決したのかをユーザーに説明してください。
- 親(例:A)をマージした後で子(例:B)をマージすると、B 固有のコミットのみが取り込まれます。
git push <fork> <branch> --force-with-leaseを実行してフォークを更新します。
フェーズ 5:クリーンアップ
- 上流に完全にマージされているリモートブランチを削除します(
git push <fork> --delete <branch>)。 - 共有ブランチをリセットした場合は、バックアップを削除する前に、
git range-diff <old-base>..sync-fork/pre-reset/<branch> <upstream>/<branch>..<branch>で古いバックアップと新しい共有ブランチを比較し、再適用されたパッチのスタックを確認します。 - すべてのバックアップブランチを削除します:
git for-each-ref --format='%(refname:short)' 'refs/heads/sync-fork/' | xargs git branch -D - フェーズ 0 で保存した元のブランチに戻ります:
git checkout <saved-branch>。 - フェーズ 0 で変更をスタッシュした場合は、復元します:
git stash pop。 - 削除可能なローカル追跡ブランチを一覧表示します。
- 同期、リベース、マージ、削除した内容と、残っているブランチをユーザーに最終報告します。
ルール
- リセットまたは強制プッシュを行う前に、必ず計画をユーザーに確認してください。
- リセットしたブランチをプッシュする際は、必ず
--force-with-leaseを使用し、--forceは使用しないでください。 - ローカルにのみ存在する作業を保持してください — 目的は、上流にまだ取り込まれていないブランチを維持しながら、上流の現在の状態に合わせることです。
- 未マージの作業があるブランチは削除しないでください — コミット SHA が異なっていても、内容がすでに上流に存在するブランチのみをクリーンアップしてください(スカッシュマージの場合など)。
- フォークの同期とは無関係なブランチには触れないでください — 共有ブランチをターゲットとしていないフィーチャーブランチはそのままにしてください。
- バックアップブランチには
sync-fork/プレフィックスを使用します — これらは一時的なもので、最後にクリーンアップします。同期が中断された場合は、復旧用に残ります。git for-each-ref --format='%(refname:short)' 'refs/heads/sync-fork/' | xargs git branch -Dを使用して手動でクリーンアップしてください。