Commit 5a6158a
feat(plugin): 旧 plugins/ コピーインストールを repos/ 永続クローンへ移行する devbase plugin migrate を追加 (#31)
* chore: PLAN04-migration Draft PR 作成
* feat(plugin): 既存 plugins/ コピーインストールを repos/ 永続クローンへ移行
PLAN04 PR2。PR1 (#29) で repos/ 永続クローン方式に切り替えたが、PR1 以前に
plugins/<name>/ へファイルコピーされた既存インストールは移行されないため、その
移行ロジックを追加する。
- migrator.py (新規):
- needs_migration / _is_legacy_plugin: legacy plugins/ インストールの検出
(linked は --link 専用として除外)
- _dirs_differ: コピーとクローンの差分検出 (内容変更・追加ファイルを保守的に差分扱い)
- migrate: 未クローン repo の永続クローン作成、InstalledPlugin.path の repos/ 書き換え、
差分なしは plugins/<name> 削除・差分ありは <name>.bak 保全、sync_projects 再実行、
--link/.bak/skip が無ければ plugins/ を .gitkeep のみに正規化
- plugin migrate サブコマンド (cli.py / commands/plugin.py)
- install/update 初回実行時に _auto_migrate で自動移行
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(plugin): migration の symlink 差分検知漏れ / .bak 上書き / registry 先行更新を修正
cross-review round 1 の major 指摘 4 件 (codex 2 / gemini 2) に対応。
- _dirs_differ: regular file のみ比較していたため legacy copy のみに存在する
symlink / 空ディレクトリ / 型不一致を差分として検知できず、後続の
shutil.rmtree で silently 削除される恐れがあった。全エントリを対象に
型 + 内容 (file は byte, symlink は target) を比較するよう厳密化
- _unique_bak_path: 既存の <name>.bak を無条件に rmtree していたため、
前回 migration で保全した未整理バックアップが消失する恐れがあった。
存在時は .bak-2, .bak-3 ... と一意名に退避するよう変更
- migrate: filesystem の退避/削除が成功してから registry.add で
plugins.yml を repos/ path に書き換えるよう順序を入れ替え。失敗時に
registry だけ先行更新され retry も効かなくなる partial state を防止
- _cleanup_plugins_dir: .bak-N 形式も保全 .bak として検知するよう調整
- 上記挙動を網羅するテスト 6 件追加
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(plugin): migration の差分判定厳密化 / partial clone 復旧 / cleanup 報告修正
cross-review round 2 の指摘対応。
- _dirs_differ: upstream 専用追加 (clone にのみ存在) を差分扱いしないよう
変更。コピー側にのみ存在するエントリ・共通エントリの型/target/内容差分の
みを preserved 判定に使い、通常の upstream 更新で不要な .bak 退避が発生
しないようにした (codex#91 / gemini#91 重複指摘)。
- _files_equal: read_bytes() の全読み込みを 64KB チャンクのストリーム比較に
置き換え、巨大ファイルでのメモリ枯渇リスクを排除 (gemini#105)。
- _ensure_repo_cloned: 前回 clone 失敗で残った partial dir (.git 無し /
registry.yml 不正) を検知して削除・再 clone するよう修正。無限に
parse 失敗を繰り返す経路を解消 (codex#132)。
- _cleanup_plugins_dir: .gitkeep でも .bak でもない想定外エントリが残る場合
は cleaned=True と報告せず False を返すよう修正 (gemini#176)。
- docs: devbase plugin migrate の CLI リファレンスを追加 (codex review body)。
テスト 4 件追加 (upstream 専用追加は差分なし / 同サイズ内容差 / partial
clone 再 clone / cleanup の想定外エントリ保持)。全 252 件 pass。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(plugin): migration の保全判定・clone 健全性・registry 保存効率を改善 (PR2 round3)
cross-review round 3 の指摘に対応:
- _cleanup_plugins_dir の `.bak` 判定を `'.bak' in name` から
`<name>.bak[-N]` 末尾一致 (_is_bak_name) に修正。my.bakery 等の誤マッチを排除
- migrate ループ内の per-plugin `registry.add` を loop 末尾の単一
`registry.add_many` に集約し plugins.yml の保存頻発を解消
(各 plugin の fs 移動と entry 構築は同一 try 内のため失敗時の retry 性は維持)
- _ensure_repo_cloned で local_path 設定済みでも .git/registry.yml を
検証 (_clone_is_healthy)。壊れた既存 dir は除去して再 clone
- _files_equal で S_IMODE を比較。旧コピーの実行ビット変更を差分扱いし保全
- _auto_migrate の preserved/skipped 再通知を loud な per-plugin WARNING から
簡潔な INFO ヒント 1 行に抑制 (詳細は devbase plugin migrate 側で出力)
migrator テスト 12 件追加 (.bak 末尾判定 / clone 健全性 / 実行ビット差分 /
batched save / broken local_path 再 clone / 警告抑制)。全 264 件 pass。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(plugin): migration の registry 重複排除 / exec ビット限定比較 / 健全 clone 保全 (PR2 round4)
- registry.add_many: 引数内で名前が重複する場合 last-wins で一意化してから
反映し、plugins.yml に矛盾エントリが残らないようにした
- _files_equal: 全権限ビット比較を exec ビット (+x) 限定に変更し、umask /
group 設定差による誤った .bak 退避を防止
- _ensure_repo_cloned: local_path 記録済みだが unhealthy な既存 dir でも
.git があれば未コミット/未 push のローカル変更を失わないよう rmtree せず
PluginError を送出し、.git 欠落 (真に壊れている) 場合のみ再 clone する
test: add_many 重複排除 / exec ビット限定 / .git 付き clone 保全の 6 件を追加
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(plugin): migration の derived clone 保全 / registry 先行永続化 / 特殊ファイル差分検知 (PR2 round5)
cross-review round 5 で指摘された 4 件に対応:
- derived clone 経路 (.git 保護): repo.local_path 未設定でも repos/<derived>
に .git 付き既存 clone があり registry.yml だけ欠ける場合、無条件 rmtree で
未コミット/未 push のローカル変更を失っていた。_reclaim_or_protect_existing
を新設し local_path 経路と同じく .git 有りは削除せず PluginError で復旧案内
する (freshly clone した分のみ破棄)。
- registry 先行永続化: 旧 plugins/ コピーの削除/.bak 退避 → add_many の順序を
逆転し、検証済み path rewrite を破壊的 fs 操作の前に 1 回保存する二相構成へ。
保存失敗時はコピー無傷で abort (次回 retry 可能)、phase2 の retire 失敗は
registry が既に有効な repos/ clone を指すため lingering copy として
_cleanup_plugins_dir が surface する (silent data loss を排除)。
- clone_dir がファイル/symlink で squat: clone_dir.is_dir() のみでは git_clone
が失敗するため、ファイル/symlink は unlink して再 clone (git tree を持たない
ため損失なし)。
- _entry_kind == 'other' (socket/pipe/device): 内容比較できず identical を
証明できないため diverged 扱いとし .bak 保全に倒す。
migrator テスト 5 件追加 (derived .git 保護 / clone_dir ファイル squat /
registry 保存失敗でコピー無傷 / retire は保存後 / fifo は差分扱い)。
全 275 件 pass。ruff (E9,F63,F7,F82) / compileall pass。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(plugin): derived clone 経路で健全 clone を reuse する (round 6)
round 5 で derived 経路 (local_path 未設定) の `.git` 付き既存 dir を
無条件に保護 (PluginError) していたが過剰だった。`repos/<derived>` に
.git + registry.yml が両方そろった健全 clone が残っている場合は
PluginError で migration を skip せず、そのまま reuse して local_path を
永続化するよう修正。
- 健全 clone (.git + registry.yml) → reuse + local_path 永続化
- .git ありだが unhealthy (registry.yml 欠落) → 従来どおり保護 (PluginError)
- .git 無し / file・symlink squat → reclaim して再 clone
local_path 経路と derived 経路で挙動を揃え、fresh clone 後と healthy reuse
後の local_path 永続化を `_persist_repo_local_path` に共通化した。
健全 derived clone が reuse され migration が skip されないことを検証する
テスト `test_derived_path_with_healthy_clone_is_reused` を追加。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* perf(plugin): migration の clone 永続化を単一保存に集約 (round 7)
_ensure_repo_cloned が clone のたびに add_repository で plugins.yml を
保存していたため、多数リポジトリ移行時に保存回数が repo 数に比例していた。
clone 済み repo 行を pending_repos に貯め、path rewrite と合わせて Phase2
(破壊的 cleanup) の直前に save_migration で 1 回だけ保存するよう変更。
二相アトミシティは維持: 旧 copy 削除より前に registry が必ず flush 済みで
あること (clone を指す local_path / plugin path の両方) を不変条件として
保持。save_migration は repos + plugins を単一 load+save で upsert する。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* perf(plugin): migration の registry.yml パースをリポジトリあたり 1 回に集約 (PR2 round8)
gemini review (migrator.py:428 [minor/performance]) 対応。
_ensure_repo_cloned が clone/reuse 時に parse_registry_yml した RegistryInfo
を戻り値で返すようにし、migrate ループ側で再パースしていた重複を解消した。
さらに _build_persisted_repo もパース済み reg_info を受け取る形に変更し、
fresh-clone 経路での二重パース (helper 内 + ループ) も排除。結果として
registry.yml の読み込みはリポジトリあたり最大 1 回 (local_path fast path は
lazy fallback) に削減。未使用になった _build_persisted_repo の registry 引数も
除去。挙動・テスト (plugin 203 / migrator 60) は不変。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* perf(plugin): migration の repo 解決をループ前に 1 回へ集約 + migrate を prefix 解決対象に追加 (PR2 round9)
- migrate ループ内の registry.get_repository_by_url (毎回 plugins.yml を再読込) を
ループ前の URL→repo 辞書索引 1 回に置換し、O(N) ディスク I/O を O(1) に集約
- SUBCMD_MAP[('plugin','pl')] に 'migrate' を追加し、devbase plugin mi / pl mi の
prefix 解決が効くよう修正 (従来は argparse エラー)
- 再読込が plugin 数に比例しないことを検証する回帰テストを追加
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(plugin): migration の repo 解決が plugin 数に比例して plugins.yml を再読込しない回帰テストを追加 (PR2 round9)
round9 で migrate ループ内の get_repository_by_url (毎回 plugins.yml 再読込) を
ループ前の URL→repo 辞書索引 1 回に置換した変更の回帰防止。_load 呼び出し回数を
計数し plugin 数より少ないことを検証する。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>1 parent 79b661f commit 5a6158a
8 files changed
Lines changed: 1783 additions & 8 deletions
File tree
- docs/user
- lib/devbase
- commands
- plugin
- tests/plugin
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
404 | 404 | | |
405 | 405 | | |
406 | 406 | | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
407 | 425 | | |
408 | 426 | | |
409 | 427 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
40 | | - | |
| 40 | + | |
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| |||
234 | 234 | | |
235 | 235 | | |
236 | 236 | | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
237 | 240 | | |
238 | 241 | | |
239 | 242 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| 38 | + | |
37 | 39 | | |
38 | 40 | | |
39 | 41 | | |
| |||
138 | 140 | | |
139 | 141 | | |
140 | 142 | | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
141 | 173 | | |
142 | 174 | | |
143 | 175 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
84 | 110 | | |
85 | 111 | | |
86 | 112 | | |
| |||
91 | 117 | | |
92 | 118 | | |
93 | 119 | | |
| 120 | + | |
| 121 | + | |
94 | 122 | | |
95 | 123 | | |
96 | 124 | | |
| |||
0 commit comments