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