gabriel / muse public
test_cmd_count_objects.py python
607 lines 22.6 KB
Raw
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor ⚠ breaking 125 days ago
1 """Tests for ``muse count-objects`` — object store diagnostics.
2
3 Coverage tiers:
4 - Unit: _count_loose_objects, _collect_reachable_ids helpers
5 - Integration: empty store, single object, multi-shard, verbose breakdown,
6 --unreachable counts GC candidates, JSON schema, text format,
7 objects match expected after N commits
8 - End-to-end: full CLI via CliRunner
9 - Security: read-only — no mutations; no content reads (stat only)
10 - Stress: store with many objects; --unreachable on multi-commit repo
11 """
12
13 from __future__ import annotations
14 from collections.abc import Mapping
15
16 import datetime
17 import json
18 import os
19 import pathlib
20
21 import pytest
22
23 from tests.cli_test_helper import CliRunner
24 from muse.core.object_store import write_object
25 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
26 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
27 from muse.core.types import Manifest, blob_id
28 from muse.core.paths import muse_dir, objects_dir, ref_path
29
30 runner = CliRunner()
31
32 _REPO_ID = "count-objects-test"
33 _counter = 0
34
35
36 # ---------------------------------------------------------------------------
37 # Helpers
38 # ---------------------------------------------------------------------------
39
40
41
42
43 def _init_repo(path: pathlib.Path) -> pathlib.Path:
44 dot_muse = muse_dir(path)
45 for d in ("commits", "snapshots", "objects", "refs/heads", "code"):
46 (dot_muse / d).mkdir(parents=True, exist_ok=True)
47 (dot_muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
48 (dot_muse / "repo.json").write_text(
49 json.dumps({"repo_id": _REPO_ID, "domain": "code"}), encoding="utf-8"
50 )
51 return path
52
53
54 def _env(repo: pathlib.Path) -> Mapping[str, str]:
55 return {"MUSE_REPO_ROOT": str(repo)}
56
57
58 def _commit_files(
59 root: pathlib.Path,
60 files: Mapping[str, bytes],
61 branch: str = "main",
62 ) -> str:
63 global _counter
64 _counter += 1
65 manifest: Manifest = {}
66 for rel_path, content in files.items():
67 obj_id = blob_id(content)
68 write_object(root, obj_id, content)
69 manifest[rel_path] = obj_id
70 abs_path = root / rel_path
71 abs_path.parent.mkdir(parents=True, exist_ok=True)
72 abs_path.write_bytes(content)
73 snap_id = compute_snapshot_id(manifest)
74 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
75 committed_at = datetime.datetime.now(datetime.timezone.utc)
76 # Read the current tip to use as parent (proper chain for reachability BFS).
77 branch_ref = ref_path(root, branch)
78 parent_id = branch_ref.read_text(encoding="utf-8").strip() if branch_ref.exists() else None
79 parents = [parent_id] if parent_id else []
80 commit_id = compute_commit_id( parent_ids=parents,
81 snapshot_id=snap_id,
82 message=f"commit {_counter}",
83 committed_at_iso=committed_at.isoformat(),
84 )
85 write_commit(
86 root,
87 CommitRecord(
88 commit_id=commit_id,
89 repo_id="test-repo",
90 branch=branch,
91 snapshot_id=snap_id,
92 message=f"commit {_counter}",
93 committed_at=committed_at,
94 parent_commit_id=parent_id,
95 ),
96 )
97 branch_ref.write_text(commit_id, encoding="utf-8")
98 return commit_id
99
100
101 def _invoke(repo: pathlib.Path, *args: str) -> "InvokeResult":
102 from muse.cli.app import main as cli
103 return runner.invoke(cli, ["count-objects", *args], env=_env(repo))
104
105
106 # ---------------------------------------------------------------------------
107 # Unit — _count_loose_objects
108 # ---------------------------------------------------------------------------
109
110
111 def test_count_loose_objects_empty_store(tmp_path: pathlib.Path) -> None:
112 from muse.cli.commands.count_objects import _count_loose_objects
113 root = _init_repo(tmp_path)
114 count, size = _count_loose_objects(root)
115 assert count == 0
116 assert size == 0
117
118
119 def test_count_loose_objects_single_object(tmp_path: pathlib.Path) -> None:
120 from muse.cli.commands.count_objects import _count_loose_objects
121 root = _init_repo(tmp_path)
122 content = b"hello world"
123 obj_id = blob_id(content)
124 write_object(root, obj_id, content)
125 count, size = _count_loose_objects(root)
126 assert count == 1
127 assert size > 0
128
129
130 def test_count_loose_objects_multiple_shards(tmp_path: pathlib.Path) -> None:
131 from muse.cli.commands.count_objects import _count_loose_objects
132 root = _init_repo(tmp_path)
133 # Write 5 distinct objects (may land in different shards)
134 for i in range(5):
135 content = f"object {i}".encode()
136 write_object(root, blob_id(content), content)
137 count, _ = _count_loose_objects(root)
138 assert count == 5
139
140
141 # ---------------------------------------------------------------------------
142 # Unit — _collect_reachable_ids
143 # ---------------------------------------------------------------------------
144
145
146 def test_collect_reachable_ids_empty_repo(tmp_path: pathlib.Path) -> None:
147 from muse.cli.commands.count_objects import _collect_reachable_ids
148 root = _init_repo(tmp_path)
149 ids = _collect_reachable_ids(root)
150 assert isinstance(ids, set)
151 assert len(ids) == 0
152
153
154 def test_collect_reachable_ids_after_commit(tmp_path: pathlib.Path) -> None:
155 from muse.cli.commands.count_objects import _collect_reachable_ids
156 root = _init_repo(tmp_path)
157 _commit_files(root, {"a.py": b"# a\n"})
158 ids = _collect_reachable_ids(root)
159 # At minimum: the blob object for a.py
160 assert len(ids) >= 1
161
162
163 def test_collect_reachable_ids_includes_all_blobs(tmp_path: pathlib.Path) -> None:
164 from muse.cli.commands.count_objects import _collect_reachable_ids
165 root = _init_repo(tmp_path)
166 files = {"a.py": b"# a\n", "b.py": b"# b\n", "c.py": b"# c\n"}
167 _commit_files(root, files)
168 ids = _collect_reachable_ids(root)
169 # All three blob IDs must be reachable
170 for content in files.values():
171 assert blob_id(content) in ids
172
173
174 # ---------------------------------------------------------------------------
175 # Integration — JSON output schema
176 # ---------------------------------------------------------------------------
177
178
179 def test_count_objects_json_schema_keys(tmp_path: pathlib.Path) -> None:
180 root = _init_repo(tmp_path)
181 _commit_files(root, {"a.py": b"# a\n"})
182 result = _invoke(root, "--json")
183 assert result.exit_code == 0
184 data = json.loads(result.stdout)
185 for key in ("loose_objects", "loose_size_kb", "total_objects", "total_size_kb",
186 "object_store_path", "duration_ms", "exit_code"):
187 assert key in data, f"Missing key: {key}"
188
189
190 def test_count_objects_json_count_matches_written(tmp_path: pathlib.Path) -> None:
191 root = _init_repo(tmp_path)
192 # Write 3 unique blobs directly (no commit overhead)
193 for i in range(3):
194 content = f"direct blob {i}".encode()
195 write_object(root, blob_id(content), content)
196 result = _invoke(root, "--json")
197 data = json.loads(result.stdout)
198 assert data["loose_objects"] >= 3
199
200
201 def test_count_objects_json_empty_store(tmp_path: pathlib.Path) -> None:
202 root = _init_repo(tmp_path)
203 result = _invoke(root, "--json")
204 assert result.exit_code == 0
205 data = json.loads(result.stdout)
206 assert data["loose_objects"] == 0
207 assert data["total_objects"] == 0
208
209
210 def test_count_objects_json_size_nonzero_after_write(tmp_path: pathlib.Path) -> None:
211 root = _init_repo(tmp_path)
212 write_object(root, blob_id(b"x" * 1000), b"x" * 1000)
213 result = _invoke(root, "--json")
214 data = json.loads(result.stdout)
215 assert data["loose_size_kb"] > 0 or data["total_size_kb"] > 0
216
217
218 def test_count_objects_json_object_store_path_present(tmp_path: pathlib.Path) -> None:
219 root = _init_repo(tmp_path)
220 result = _invoke(root, "--json")
221 data = json.loads(result.stdout)
222 assert "objects" in data["object_store_path"]
223
224
225 # ---------------------------------------------------------------------------
226 # Integration — text output format
227 # ---------------------------------------------------------------------------
228
229
230 def test_count_objects_text_output_nonempty(tmp_path: pathlib.Path) -> None:
231 root = _init_repo(tmp_path)
232 _commit_files(root, {"a.py": b"# a\n"})
233 result = _invoke(root)
234 assert result.exit_code == 0
235 assert result.stdout.strip()
236
237
238 def test_count_objects_text_mentions_count(tmp_path: pathlib.Path) -> None:
239 root = _init_repo(tmp_path)
240 for i in range(5):
241 write_object(root, blob_id(f"obj{i}".encode()), f"obj{i}".encode())
242 result = _invoke(root)
243 # The count should appear somewhere in the output
244 assert any(char.isdigit() for char in result.stdout)
245
246
247 # ---------------------------------------------------------------------------
248 # Integration — --verbose shard breakdown
249 # ---------------------------------------------------------------------------
250
251
252 def test_count_objects_verbose_json_has_shards(tmp_path: pathlib.Path) -> None:
253 root = _init_repo(tmp_path)
254 for i in range(4):
255 content = f"shard content {i}".encode()
256 write_object(root, blob_id(content), content)
257 result = _invoke(root, "--verbose", "--json")
258 assert result.exit_code == 0
259 data = json.loads(result.stdout)
260 assert "shards" in data
261 assert isinstance(data["shards"], list)
262
263
264 def test_count_objects_verbose_shards_sum_to_total(tmp_path: pathlib.Path) -> None:
265 root = _init_repo(tmp_path)
266 for i in range(6):
267 content = f"v content {i}".encode()
268 write_object(root, blob_id(content), content)
269 result = _invoke(root, "--verbose", "--json")
270 data = json.loads(result.stdout)
271 shard_total = sum(s["count"] for s in data["shards"])
272 assert shard_total == data["loose_objects"]
273
274
275 # ---------------------------------------------------------------------------
276 # Integration — --unreachable
277 # ---------------------------------------------------------------------------
278
279
280 def test_count_objects_unreachable_zero_after_clean_commit(tmp_path: pathlib.Path) -> None:
281 """After a commit where all blobs are referenced, unreachable should be 0."""
282 root = _init_repo(tmp_path)
283 _commit_files(root, {"a.py": b"# a\n", "b.py": b"# b\n"})
284 result = _invoke(root, "--unreachable", "--json")
285 assert result.exit_code == 0
286 data = json.loads(result.stdout)
287 assert "unreachable_objects" in data
288 assert data["unreachable_objects"] == 0
289
290
291 def test_count_objects_unreachable_detects_orphan_blobs(tmp_path: pathlib.Path) -> None:
292 """Blobs written but not referenced by any commit are unreachable."""
293 root = _init_repo(tmp_path)
294 _commit_files(root, {"a.py": b"# a\n"})
295 # Write an extra blob that is NOT referenced by any commit
296 orphan = b"i am an orphan blob"
297 write_object(root, blob_id(orphan), orphan)
298 result = _invoke(root, "--unreachable", "--json")
299 data = json.loads(result.stdout)
300 assert data["unreachable_objects"] >= 1
301
302
303 def test_count_objects_unreachable_empty_repo(tmp_path: pathlib.Path) -> None:
304 root = _init_repo(tmp_path)
305 result = _invoke(root, "--unreachable", "--json")
306 assert result.exit_code == 0
307 data = json.loads(result.stdout)
308 assert data["unreachable_objects"] == 0
309
310
311 # ---------------------------------------------------------------------------
312 # Security — read-only, no mutations
313 # ---------------------------------------------------------------------------
314
315
316 def test_count_objects_does_not_modify_store(tmp_path: pathlib.Path) -> None:
317 """count-objects must not write, delete, or move any object files."""
318 root = _init_repo(tmp_path)
319 _commit_files(root, {"a.py": b"# a\n"})
320 obj_dir = objects_dir(root)
321 # Collect (path, mtime) before
322 before = {
323 str(p): p.stat().st_mtime
324 for p in obj_dir.rglob("*")
325 if p.is_file()
326 }
327 _invoke(root, "--json")
328 _invoke(root, "--unreachable", "--json")
329 # Collect after
330 after = {
331 str(p): p.stat().st_mtime
332 for p in obj_dir.rglob("*")
333 if p.is_file()
334 }
335 assert before == after, "count-objects modified the object store"
336
337
338 # ---------------------------------------------------------------------------
339 # Stress
340 # ---------------------------------------------------------------------------
341
342
343 def test_count_objects_large_store(tmp_path: pathlib.Path) -> None:
344 """Store with 200 objects — count should be accurate."""
345 root = _init_repo(tmp_path)
346 for i in range(200):
347 content = f"stress object {i:04d}".encode()
348 write_object(root, blob_id(content), content)
349 result = _invoke(root, "--json")
350 assert result.exit_code == 0
351 data = json.loads(result.stdout)
352 assert data["loose_objects"] == 200
353
354
355 def test_count_objects_unreachable_large_repo(tmp_path: pathlib.Path) -> None:
356 """10 commits with 10 files each — all referenced, unreachable = 0."""
357 root = _init_repo(tmp_path)
358 for i in range(10):
359 files = {f"pkg/file_{i}_{j}.py": f"# {i},{j}\n".encode() for j in range(10)}
360 _commit_files(root, files)
361 result = _invoke(root, "--unreachable", "--json")
362 assert result.exit_code == 0
363 data = json.loads(result.stdout)
364 assert data["unreachable_objects"] == 0
365
366
367 # ---------------------------------------------------------------------------
368 # TestJsonSchemaComplete
369 # ---------------------------------------------------------------------------
370
371
372 _REQUIRED_KEYS = frozenset({
373 "loose_objects",
374 "loose_size_kb",
375 "total_objects",
376 "total_size_kb",
377 "object_store_path",
378 "duration_ms",
379 "exit_code",
380 })
381
382 _REQUIRED_KEYS_UNREACHABLE = _REQUIRED_KEYS | {"unreachable_objects"}
383 _REQUIRED_KEYS_VERBOSE = _REQUIRED_KEYS | {"shards"}
384
385
386 class TestJsonSchemaComplete:
387 """Every required key must appear in every JSON output path."""
388
389 def test_base_keys_present(self, tmp_path: pathlib.Path) -> None:
390 root = _init_repo(tmp_path)
391 result = _invoke(root, "--json")
392 assert result.exit_code == 0
393 data = json.loads(result.stdout)
394 missing = _REQUIRED_KEYS - data.keys()
395 assert not missing, f"Missing keys: {missing}"
396
397 def test_unreachable_keys_present(self, tmp_path: pathlib.Path) -> None:
398 root = _init_repo(tmp_path)
399 result = _invoke(root, "--unreachable", "--json")
400 assert result.exit_code == 0
401 data = json.loads(result.stdout)
402 missing = _REQUIRED_KEYS_UNREACHABLE - data.keys()
403 assert not missing, f"Missing keys: {missing}"
404
405 def test_verbose_keys_present(self, tmp_path: pathlib.Path) -> None:
406 root = _init_repo(tmp_path)
407 result = _invoke(root, "--verbose", "--json")
408 assert result.exit_code == 0
409 data = json.loads(result.stdout)
410 missing = _REQUIRED_KEYS_VERBOSE - data.keys()
411 assert not missing, f"Missing keys: {missing}"
412
413 def test_all_flags_keys_present(self, tmp_path: pathlib.Path) -> None:
414 root = _init_repo(tmp_path)
415 result = _invoke(root, "--unreachable", "--verbose", "--json")
416 assert result.exit_code == 0
417 data = json.loads(result.stdout)
418 missing = (_REQUIRED_KEYS_UNREACHABLE | _REQUIRED_KEYS_VERBOSE) - data.keys()
419 assert not missing, f"Missing keys: {missing}"
420
421 def test_exit_code_field_is_zero_on_success(self, tmp_path: pathlib.Path) -> None:
422 root = _init_repo(tmp_path)
423 result = _invoke(root, "--json")
424 assert result.exit_code == 0
425 assert json.loads(result.stdout)["exit_code"] == 0
426
427 def test_exit_code_is_integer(self, tmp_path: pathlib.Path) -> None:
428 root = _init_repo(tmp_path)
429 result = _invoke(root, "--json")
430 assert isinstance(json.loads(result.stdout)["exit_code"], int)
431
432 def test_json_is_compact(self, tmp_path: pathlib.Path) -> None:
433 root = _init_repo(tmp_path)
434 result = _invoke(root, "--json")
435 lines = [ln for ln in result.stdout.splitlines() if ln.strip()]
436 assert len(lines) == 1, "JSON output must be a single line"
437
438 def test_exit_code_in_json_matches_process_exit(self, tmp_path: pathlib.Path) -> None:
439 root = _init_repo(tmp_path)
440 result = _invoke(root, "--json")
441 assert json.loads(result.stdout)["exit_code"] == result.exit_code
442
443
444 # ---------------------------------------------------------------------------
445 # TestElapsedSeconds
446 # ---------------------------------------------------------------------------
447
448
449 class TestElapsedSeconds:
450 """``duration_ms`` must be a non-negative float in every JSON path."""
451
452 def _assert_elapsed(self, data: Mapping[str, object]) -> None: # type: ignore[type-arg]
453 assert "duration_ms" in data
454 assert isinstance(data["duration_ms"], float)
455 assert data["duration_ms"] >= 0.0
456
457 def test_elapsed_base(self, tmp_path: pathlib.Path) -> None:
458 root = _init_repo(tmp_path)
459 result = _invoke(root, "--json")
460 self._assert_elapsed(json.loads(result.stdout))
461
462 def test_elapsed_with_unreachable(self, tmp_path: pathlib.Path) -> None:
463 root = _init_repo(tmp_path)
464 _commit_files(root, {"a.py": b"# a\n"})
465 result = _invoke(root, "--unreachable", "--json")
466 self._assert_elapsed(json.loads(result.stdout))
467
468 def test_elapsed_with_verbose(self, tmp_path: pathlib.Path) -> None:
469 root = _init_repo(tmp_path)
470 _commit_files(root, {"a.py": b"# a\n"})
471 result = _invoke(root, "--verbose", "--json")
472 self._assert_elapsed(json.loads(result.stdout))
473
474 def test_elapsed_all_flags(self, tmp_path: pathlib.Path) -> None:
475 root = _init_repo(tmp_path)
476 _commit_files(root, {"a.py": b"# a\n"})
477 result = _invoke(root, "--unreachable", "--verbose", "--json")
478 self._assert_elapsed(json.loads(result.stdout))
479
480 def test_elapsed_is_float_not_int(self, tmp_path: pathlib.Path) -> None:
481 root = _init_repo(tmp_path)
482 result = _invoke(root, "--json")
483 data = json.loads(result.stdout)
484 assert isinstance(data["duration_ms"], float)
485
486 def test_elapsed_reasonable_upper_bound(self, tmp_path: pathlib.Path) -> None:
487 root = _init_repo(tmp_path)
488 result = _invoke(root, "--json")
489 assert json.loads(result.stdout)["duration_ms"] < 5.0
490
491 def test_elapsed_six_decimal_places(self, tmp_path: pathlib.Path) -> None:
492 root = _init_repo(tmp_path)
493 result = _invoke(root, "--json")
494 elapsed = json.loads(result.stdout)["duration_ms"]
495 assert round(elapsed, 6) == elapsed
496
497
498 # ---------------------------------------------------------------------------
499 # TestExitCode
500 # ---------------------------------------------------------------------------
501
502
503 class TestExitCode:
504 """``exit_code`` in JSON must mirror the process exit code."""
505
506 def test_exit_code_zero_empty_store(self, tmp_path: pathlib.Path) -> None:
507 root = _init_repo(tmp_path)
508 result = _invoke(root, "--json")
509 assert result.exit_code == 0
510 assert json.loads(result.stdout)["exit_code"] == 0
511
512 def test_exit_code_zero_with_objects(self, tmp_path: pathlib.Path) -> None:
513 root = _init_repo(tmp_path)
514 _commit_files(root, {"a.py": b"# a\n"})
515 result = _invoke(root, "--json")
516 assert result.exit_code == 0
517 assert json.loads(result.stdout)["exit_code"] == 0
518
519 def test_exit_code_zero_unreachable_flag(self, tmp_path: pathlib.Path) -> None:
520 root = _init_repo(tmp_path)
521 _commit_files(root, {"a.py": b"# a\n"})
522 result = _invoke(root, "--unreachable", "--json")
523 assert result.exit_code == 0
524 assert json.loads(result.stdout)["exit_code"] == 0
525
526 def test_exit_code_zero_verbose_flag(self, tmp_path: pathlib.Path) -> None:
527 root = _init_repo(tmp_path)
528 result = _invoke(root, "--verbose", "--json")
529 assert result.exit_code == 0
530 assert json.loads(result.stdout)["exit_code"] == 0
531
532 def test_exit_code_matches_process_exit(self, tmp_path: pathlib.Path) -> None:
533 root = _init_repo(tmp_path)
534 result = _invoke(root, "--json")
535 assert json.loads(result.stdout)["exit_code"] == result.exit_code
536
537
538 # ---------------------------------------------------------------------------
539 # Data integrity — unreachable detection correctness with sha256: prefix
540 # ---------------------------------------------------------------------------
541
542
543 class TestUnreachableDetection:
544 """Verify unreachable detection correctly handles sha256:-prefixed IDs."""
545
546 def test_all_committed_blobs_reachable(self, tmp_path: pathlib.Path) -> None:
547 root = _init_repo(tmp_path)
548 _commit_files(root, {"x.py": b"x = 1\n", "y.py": b"y = 2\n"})
549 result = _invoke(root, "--unreachable", "--json")
550 assert result.exit_code == 0
551 assert json.loads(result.stdout)["unreachable_objects"] == 0
552
553 def test_orphan_blob_detected(self, tmp_path: pathlib.Path) -> None:
554 root = _init_repo(tmp_path)
555 _commit_files(root, {"a.py": b"# committed\n"})
556 write_object(root, blob_id(b"orphan"), b"orphan")
557 result = _invoke(root, "--unreachable", "--json")
558 assert json.loads(result.stdout)["unreachable_objects"] >= 1
559
560 def test_multiple_orphans_all_counted(self, tmp_path: pathlib.Path) -> None:
561 root = _init_repo(tmp_path)
562 _commit_files(root, {"a.py": b"# committed\n"})
563 for i in range(5):
564 content = f"orphan {i}".encode()
565 write_object(root, blob_id(content), content)
566 result = _invoke(root, "--unreachable", "--json")
567 assert json.loads(result.stdout)["unreachable_objects"] >= 5
568
569 def test_reachable_set_uses_sha256_prefix(self, tmp_path: pathlib.Path) -> None:
570 """_collect_reachable_ids must return sha256:-prefixed IDs."""
571 from muse.cli.commands.count_objects import _collect_reachable_ids
572 root = _init_repo(tmp_path)
573 content = b"# test\n"
574 _commit_files(root, {"a.py": content})
575 ids = _collect_reachable_ids(root)
576 assert len(ids) > 0
577 for obj_id in ids:
578 assert obj_id.startswith("sha256:"), f"ID missing sha256: prefix: {obj_id!r}"
579
580
581 class TestRegisterFlags:
582 def test_default_json_out_is_false(self) -> None:
583 import argparse
584 from muse.cli.commands.count_objects import register
585 p = argparse.ArgumentParser()
586 subs = p.add_subparsers()
587 register(subs)
588 args = p.parse_args(["count-objects"])
589 assert args.json_out is False
590
591 def test_json_flag_sets_json_out(self) -> None:
592 import argparse
593 from muse.cli.commands.count_objects import register
594 p = argparse.ArgumentParser()
595 subs = p.add_subparsers()
596 register(subs)
597 args = p.parse_args(["count-objects", "--json"])
598 assert args.json_out is True
599
600 def test_j_shorthand_sets_json_out(self) -> None:
601 import argparse
602 from muse.cli.commands.count_objects import register
603 p = argparse.ArgumentParser()
604 subs = p.add_subparsers()
605 register(subs)
606 args = p.parse_args(["count-objects", "-j"])
607 assert args.json_out is True
File History 1 commit
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor 125 days ago