pack_objects.py python
141 lines 4.7 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 33 days ago
1 """muse plumbing pack-objects — build a PackBundle and write to stdout.
2
3 Collects a set of commits (and all referenced snapshots and objects) into a
4 single msgpack PackBundle suitable for transport to a remote. Analogous to
5 ``git pack-objects`` using a binary packfile format — efficient binary
6 encoding with raw bytes for object content (no base64 overhead).
7
8 Usage::
9
10 muse plumbing pack-objects <want_id>... [--have <id>...]
11
12 The ``--have`` IDs are commits the receiver already has. Objects reachable
13 exclusively from ``--have`` ancestors are pruned from the bundle.
14
15 Output: a PackBundle msgpack binary written to stdout (pipe to a file or HTTP
16 request body).
17
18 Plumbing contract
19 -----------------
20
21 - Exit 0: pack written to stdout.
22 - Exit 1: a wanted commit not found or HEAD has no commits.
23 - Exit 3: I/O error reading objects or snapshots from the local store.
24
25 Agent use — dry-run inspection
26 -------------------------------
27
28 Agents can inspect what *would* be packed without producing binary output::
29
30 muse plumbing pack-objects HEAD --dry-run
31 # → {"want": [...], "have": [...], "commits": 3, "snapshots": 3, "objects": 12}
32 """
33
34 from __future__ import annotations
35
36 import argparse
37 import json
38 import logging
39 import sys
40
41 import msgpack
42
43 from muse.core.errors import ExitCode
44 from muse.core.pack import build_pack
45 from muse.core.repo import require_repo
46 from muse.core.store import get_head_commit_id, read_current_branch
47 from muse.core.validation import validate_object_id
48
49 logger = logging.getLogger(__name__)
50
51
52 def register(subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None:
53 """Register the pack-objects subcommand."""
54 parser = subparsers.add_parser(
55 "pack-objects",
56 help="Build a PackBundle JSON from wanted commits and write to stdout.",
57 description=__doc__,
58 formatter_class=argparse.RawDescriptionHelpFormatter,
59 )
60 parser.add_argument(
61 "want",
62 nargs="+",
63 help="Commit IDs to pack. May be full hex IDs or 'HEAD'.",
64 )
65 parser.add_argument(
66 "--have",
67 action="append",
68 default=[],
69 dest="have",
70 metavar="COMMIT_ID",
71 help="Commits the receiver already has (pruned from pack). Repeat for multiple.",
72 )
73 parser.add_argument(
74 "--dry-run",
75 action="store_true",
76 dest="dry_run",
77 help=(
78 "Print pack summary as JSON instead of writing binary msgpack. "
79 "Agents can use this to inspect pack contents without piping binary."
80 ),
81 )
82 parser.set_defaults(func=run)
83
84
85 def run(args: argparse.Namespace) -> None:
86 """Build a PackBundle JSON from wanted commits and write to stdout.
87
88 Traverses the commit graph from each ``want`` ID, collecting all
89 commits, snapshots, and objects not already reachable from ``--have``
90 ancestors. The resulting binary bundle can be piped directly to
91 ``muse plumbing unpack-objects`` on the receiving side, or sent via
92 HTTP to a MuseHub endpoint.
93
94 Use ``--dry-run`` to receive a JSON summary instead of binary output —
95 safer for agent pipelines that just want to know the pack size.
96 """
97 want: list[str] = args.want
98 have: list[str] = args.have
99 dry_run: bool = args.dry_run
100
101 root = require_repo()
102
103 # Resolve "HEAD" → commit ID and validate all other IDs upfront so
104 # we fail loudly instead of silently producing empty packs.
105 resolved_wants: list[str] = []
106 for w in want:
107 if w.upper() == "HEAD":
108 branch = read_current_branch(root)
109 cid = get_head_commit_id(root, branch)
110 if cid is None:
111 print(json.dumps({"error": "HEAD has no commits"}), file=sys.stderr)
112 raise SystemExit(ExitCode.USER_ERROR)
113 resolved_wants.append(cid)
114 else:
115 try:
116 validate_object_id(w)
117 except ValueError as exc:
118 print(json.dumps({"error": f"Invalid want ID: {exc}"}), file=sys.stderr)
119 raise SystemExit(ExitCode.USER_ERROR)
120 resolved_wants.append(w)
121
122 for h in have:
123 try:
124 validate_object_id(h)
125 except ValueError as exc:
126 print(json.dumps({"error": f"Invalid --have ID: {exc}"}), file=sys.stderr)
127 raise SystemExit(ExitCode.USER_ERROR)
128
129 bundle = build_pack(root, commit_ids=resolved_wants, have=have)
130
131 if dry_run:
132 print(json.dumps({
133 "want": resolved_wants,
134 "have": have,
135 "commits": len(bundle["commits"]),
136 "snapshots": len(bundle["snapshots"]),
137 "objects": len(bundle["objects"]),
138 }))
139 return
140
141 sys.stdout.buffer.write(msgpack.packb(bundle, use_bin_type=True))
File History 2 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 33 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62 baseline: rc14 re-baseline after rc3 store corruption recovery Human patch 33 days ago