gabriel / muse public
fix_snapshot_ids.py python
129 lines 4.7 KB
Raw
sha256:e8214e0062ef8ef0af999937df2731655b6082781fc22bc563f58db2f42b1de9 Merge 'bump/muse-v0.2.1' into 'dev' — proposal: chore: bump… Human 11 days ago
1 """``muse code fix-snapshot-ids`` — unified-store snapshot-content-ID repair.
2
3 Standalone, explicitly-invoked repair tool for muse#83: a commit whose
4 declared ``snapshot_id`` never matched its own manifest, since creation.
5 Deliberately separate from ``muse code migrate`` — see
6 ``muse.core.snapshot_content_migrate``'s module docstring and
7 ``docs/issues/snapshot-content-id-migration-plan.md`` for why.
8
9 Usage::
10
11 muse code fix-snapshot-ids --dry-run --json # inspect, zero writes
12 muse code fix-snapshot-ids --json # execute (requires a
13 # signing identity —
14 # every rewritten
15 # commit is re-signed)
16
17 Output::
18
19 {
20 "snapshot_id_map": {"sha256:<old>": "sha256:<new>", ...},
21 "commit_id_map": {"sha256:<old>": "sha256:<new>", ...},
22 "snapshots_written": <int>,
23 "commits_written": <int>,
24 "commits_signed": <int>,
25 "refs_updated": <int>,
26 "remote_refs_updated": <int>,
27 "reflogs_updated": <int>,
28 "dry_run": true | false
29 }
30 """
31
32 import argparse
33 import json
34 import sys
35
36 from muse.core.errors import ExitCode
37 from muse.core.repo import require_repo
38 from muse.core.snapshot_content_migrate import migrate_snapshot_content_and_cascade
39
40
41 def register(subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None:
42 parser = subparsers.add_parser(
43 "fix-snapshot-ids",
44 help="Repair commits whose declared snapshot_id never matched their manifest.",
45 description=__doc__,
46 formatter_class=argparse.RawDescriptionHelpFormatter,
47 )
48 parser.add_argument(
49 "--dry-run",
50 action="store_true",
51 default=False,
52 help="Print the id_maps and counts but make no writes (default: False).",
53 )
54 parser.add_argument(
55 "--json", "-j",
56 dest="json_out",
57 action="store_true",
58 default=False,
59 help="Emit machine-readable JSON.",
60 )
61 parser.set_defaults(func=run)
62
63
64 def run(args: argparse.Namespace) -> None:
65 json_out: bool = args.json_out
66 dry_run: bool = args.dry_run
67
68 root = require_repo()
69
70 signing_identity = None
71 from muse.cli.config import get_signing_identity, list_remotes
72 signing_identity = get_signing_identity(repo_root=root)
73 if signing_identity is None:
74 for remote in list_remotes(repo_root=root):
75 signing_identity = get_signing_identity(repo_root=root, remote_url=remote["url"])
76 if signing_identity is not None:
77 break
78
79 if signing_identity is None:
80 msg = (
81 "No signing identity found. Every commit this tool rewrites is "
82 "re-signed — run `muse auth keygen` or connect a hub with "
83 "`muse hub connect` so credentials can be resolved."
84 )
85 if json_out:
86 print(json.dumps({"error": msg}))
87 else:
88 print(f"❌ {msg}", file=sys.stderr)
89 raise SystemExit(ExitCode.USER_ERROR)
90
91 try:
92 result = migrate_snapshot_content_and_cascade(
93 root, signing_identity=signing_identity, dry_run=dry_run,
94 )
95 except OSError as exc:
96 msg = f"I/O error during snapshot-content-ID repair: {exc}"
97 if json_out:
98 print(json.dumps({"error": msg}))
99 else:
100 print(f"❌ {msg}", file=sys.stderr)
101 raise SystemExit(ExitCode.INTERNAL_ERROR)
102
103 if json_out:
104 print(json.dumps({
105 "snapshot_id_map": result.snapshot_id_map,
106 "commit_id_map": result.commit_id_map,
107 "snapshots_written": result.snapshots_written,
108 "commits_written": result.commits_written,
109 "commits_signed": result.commits_signed,
110 "refs_updated": result.refs_updated,
111 "remote_refs_updated": result.remote_refs_updated,
112 "reflogs_updated": result.reflogs_updated,
113 "dry_run": result.dry_run,
114 }))
115 return
116
117 prefix = "[dry-run] " if dry_run else ""
118 print(f"{prefix}snapshots corrected: {len(result.snapshot_id_map)}")
119 print(f"{prefix}commits rewritten: {len(result.commit_id_map)}")
120 print(f"{prefix}commits signed: {result.commits_signed}")
121 print(f"{prefix}refs updated: {result.refs_updated}")
122 print(f"{prefix}remote refs updated: {result.remote_refs_updated}")
123 print(f"{prefix}reflogs updated: {result.reflogs_updated}")
124 if result.commit_id_map:
125 print(f"\n{prefix}Commit ID map ({len(result.commit_id_map)} changed):")
126 for old, new in result.commit_id_map.items():
127 print(f" {old} → {new}")
128 else:
129 print(f"\n{prefix}No commits needed rewriting.")
File History 1 commit
sha256:e8214e0062ef8ef0af999937df2731655b6082781fc22bc563f58db2f42b1de9 Merge 'bump/muse-v0.2.1' into 'dev' — proposal: chore: bump… Human 11 days ago