gabriel / muse public
test_pack_objects_supercharge.py python
538 lines 21.6 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 138 days ago
1 """Supercharge tests for ``muse pack-objects``, ``unpack-objects``, and ``verify-pack``.
2
3 TDD — [RED] tests fail until the feature lands; [GREEN] tests fill existing gaps.
4
5 New features under test
6 -----------------------
7 - ``duration_ms`` [RED] — wall-clock ms in every JSON output path
8 - ``exit_code`` [RED] — always present in every JSON output path
9 - ``object_bytes`` [RED] — total raw bytes in ``pack-objects --dry-run``
10
11 Gap-fill coverage [GREEN]
12 --------------------------
13 - dry-run keys validated exhaustively (want, have, commits, snapshots, objects)
14 - unpack round-trip output fields present (commits_written, objects_written, …)
15 - verify-pack all_ok field and failures list
16 - stat mode counts correct
17 """
18 from __future__ import annotations
19
20 import datetime
21 import hashlib
22 import json
23 import pathlib
24
25 import msgpack
26 import pytest
27
28 from muse.core.errors import ExitCode
29 from muse.core.object_store import write_object
30 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
31 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
32 from tests.cli_test_helper import CliRunner, InvokeResult
33 from muse.core._types import long_id
34
35 runner = CliRunner()
36
37 _TS = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
38
39
40 # ---------------------------------------------------------------------------
41 # Shared helpers
42 # ---------------------------------------------------------------------------
43
44 def _make_repo(tmp_path: pathlib.Path) -> pathlib.Path:
45 repo = tmp_path / "repo"
46 muse = repo / ".muse"
47 for sub in ("objects", "commits", "snapshots", "refs/heads"):
48 (muse / sub).mkdir(parents=True)
49 (muse / "HEAD").write_text("ref: refs/heads/main")
50 (muse / "repo.json").write_text(json.dumps({"repo_id": "test-repo", "domain": "code"}))
51 return repo
52
53
54 def _write_obj(repo: pathlib.Path, content: bytes) -> str:
55 digest = hashlib.sha256(content).hexdigest()
56 oid = long_id(digest)
57 write_object(repo, oid, content)
58 return oid
59
60
61 def _commit(
62 repo: pathlib.Path,
63 msg: str,
64 manifest: dict[str, str],
65 *,
66 branch: str = "main",
67 parent: str | None = None,
68 ) -> str:
69 sid = compute_snapshot_id(manifest)
70 write_snapshot(repo, SnapshotRecord(snapshot_id=sid, manifest=manifest, created_at=_TS))
71 parent_ids = [parent] if parent else []
72 cid = compute_commit_id(parent_ids, sid, msg, _TS.isoformat())
73 write_commit(repo, CommitRecord(
74 commit_id=cid, repo_id="test-repo", branch=branch,
75 snapshot_id=sid, message=msg, committed_at=_TS,
76 author="gabriel", parent_commit_id=parent, parent2_commit_id=None,
77 ))
78 ref = repo / ".muse" / "refs" / "heads" / branch
79 ref.parent.mkdir(parents=True, exist_ok=True)
80 ref.write_text(cid)
81 return cid
82
83
84 def _pack(repo: pathlib.Path, *args: str) -> InvokeResult:
85 return runner.invoke(None, ["pack-objects", *args], env={"MUSE_REPO_ROOT": str(repo)})
86
87
88 def _unpack(repo: pathlib.Path, bundle: bytes, *args: str) -> InvokeResult:
89 return runner.invoke(
90 None, ["unpack-objects", *args],
91 env={"MUSE_REPO_ROOT": str(repo)},
92 input=bundle,
93 )
94
95
96 def _verify(repo: pathlib.Path, bundle: bytes, *args: str) -> InvokeResult:
97 return runner.invoke(
98 None, ["verify-pack", *args],
99 env={"MUSE_REPO_ROOT": str(repo)},
100 input=bundle,
101 )
102
103
104 def _make_bundle(repo: pathlib.Path) -> bytes:
105 """Pack HEAD and return raw msgpack bytes."""
106 oid = _write_obj(repo, b"hello")
107 _commit(repo, "init", {"f.py": oid})
108 r = _pack(repo, "HEAD")
109 assert r.exit_code == 0, r.output
110 return r.stdout_bytes # raw binary from stdout.buffer
111
112
113 def _json_out(r: InvokeResult) -> dict:
114 for line in r.output.splitlines():
115 line = line.strip()
116 if line.startswith("{"):
117 return json.loads(line)
118 raise ValueError(f"No JSON in output:\n{r.output!r}")
119
120
121 # ---------------------------------------------------------------------------
122 # pack-objects --dry-run: duration_ms, exit_code, object_bytes [RED]
123 # ---------------------------------------------------------------------------
124
125 class TestPackObjectsDryRunSupercharge:
126 """[RED] New fields in --dry-run JSON output."""
127
128 def test_dry_run_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
129 repo = _make_repo(tmp_path)
130 oid = _write_obj(repo, b"x")
131 _commit(repo, "init", {"f.py": oid})
132 r = _pack(repo, "HEAD", "--dry-run")
133 assert r.exit_code == 0
134 d = _json_out(r)
135 assert "duration_ms" in d
136
137 def test_dry_run_duration_ms_non_negative(self, tmp_path: pathlib.Path) -> None:
138 repo = _make_repo(tmp_path)
139 oid = _write_obj(repo, b"x")
140 _commit(repo, "init", {"f.py": oid})
141 r = _pack(repo, "HEAD", "--dry-run")
142 d = _json_out(r)
143 assert d["duration_ms"] >= 0.0
144
145 def test_dry_run_has_exit_code(self, tmp_path: pathlib.Path) -> None:
146 repo = _make_repo(tmp_path)
147 oid = _write_obj(repo, b"x")
148 _commit(repo, "init", {"f.py": oid})
149 r = _pack(repo, "HEAD", "--dry-run")
150 d = _json_out(r)
151 assert "exit_code" in d
152
153 def test_dry_run_exit_code_is_zero_on_success(self, tmp_path: pathlib.Path) -> None:
154 repo = _make_repo(tmp_path)
155 oid = _write_obj(repo, b"x")
156 _commit(repo, "init", {"f.py": oid})
157 r = _pack(repo, "HEAD", "--dry-run")
158 d = _json_out(r)
159 assert d["exit_code"] == 0
160
161 def test_dry_run_has_object_bytes(self, tmp_path: pathlib.Path) -> None:
162 repo = _make_repo(tmp_path)
163 content = b"some content here"
164 oid = _write_obj(repo, content)
165 _commit(repo, "init", {"f.py": oid})
166 r = _pack(repo, "HEAD", "--dry-run")
167 d = _json_out(r)
168 assert "object_bytes" in d
169
170 def test_dry_run_object_bytes_matches_content_size(self, tmp_path: pathlib.Path) -> None:
171 repo = _make_repo(tmp_path)
172 content = b"x" * 256
173 oid = _write_obj(repo, content)
174 _commit(repo, "init", {"f.py": oid})
175 r = _pack(repo, "HEAD", "--dry-run")
176 d = _json_out(r)
177 assert d["object_bytes"] == 256
178
179 def test_dry_run_object_bytes_sums_multiple_objects(self, tmp_path: pathlib.Path) -> None:
180 repo = _make_repo(tmp_path)
181 oid_a = _write_obj(repo, b"a" * 100)
182 oid_b = _write_obj(repo, b"b" * 200)
183 _commit(repo, "init", {"a.py": oid_a, "b.py": oid_b})
184 r = _pack(repo, "HEAD", "--dry-run")
185 d = _json_out(r)
186 assert d["object_bytes"] == 300
187
188 def test_dry_run_object_bytes_is_int(self, tmp_path: pathlib.Path) -> None:
189 repo = _make_repo(tmp_path)
190 oid = _write_obj(repo, b"y")
191 _commit(repo, "init", {"f.py": oid})
192 r = _pack(repo, "HEAD", "--dry-run")
193 d = _json_out(r)
194 assert isinstance(d["object_bytes"], int)
195
196 def test_dry_run_object_bytes_zero_for_empty_pack(self, tmp_path: pathlib.Path) -> None:
197 """--have HEAD means nothing new to pack → 0 objects → 0 bytes."""
198 repo = _make_repo(tmp_path)
199 oid = _write_obj(repo, b"z")
200 cid = _commit(repo, "init", {"f.py": oid})
201 r = _pack(repo, cid, "--have", cid, "--dry-run")
202 d = _json_out(r)
203 assert d["object_bytes"] == 0
204
205
206 # ---------------------------------------------------------------------------
207 # pack-objects --dry-run: existing fields still present [GREEN]
208 # ---------------------------------------------------------------------------
209
210 class TestPackObjectsDryRunGreen:
211 """[GREEN] Existing dry-run fields remain after adding new ones."""
212
213 def test_want_field_present(self, tmp_path: pathlib.Path) -> None:
214 repo = _make_repo(tmp_path)
215 oid = _write_obj(repo, b"x")
216 _commit(repo, "init", {"f.py": oid})
217 d = _json_out(_pack(repo, "HEAD", "--dry-run"))
218 assert "want" in d
219
220 def test_have_field_present(self, tmp_path: pathlib.Path) -> None:
221 repo = _make_repo(tmp_path)
222 oid = _write_obj(repo, b"x")
223 _commit(repo, "init", {"f.py": oid})
224 d = _json_out(_pack(repo, "HEAD", "--dry-run"))
225 assert "have" in d
226
227 def test_commits_field_present(self, tmp_path: pathlib.Path) -> None:
228 repo = _make_repo(tmp_path)
229 oid = _write_obj(repo, b"x")
230 _commit(repo, "init", {"f.py": oid})
231 d = _json_out(_pack(repo, "HEAD", "--dry-run"))
232 assert "commits" in d
233
234 def test_snapshots_field_present(self, tmp_path: pathlib.Path) -> None:
235 repo = _make_repo(tmp_path)
236 oid = _write_obj(repo, b"x")
237 _commit(repo, "init", {"f.py": oid})
238 d = _json_out(_pack(repo, "HEAD", "--dry-run"))
239 assert "snapshots" in d
240
241 def test_objects_field_present(self, tmp_path: pathlib.Path) -> None:
242 repo = _make_repo(tmp_path)
243 oid = _write_obj(repo, b"x")
244 _commit(repo, "init", {"f.py": oid})
245 d = _json_out(_pack(repo, "HEAD", "--dry-run"))
246 assert "objects" in d
247
248 def test_have_pruning_reduces_objects(self, tmp_path: pathlib.Path) -> None:
249 repo = _make_repo(tmp_path)
250 oid = _write_obj(repo, b"v1")
251 c1 = _commit(repo, "c1", {"f.py": oid})
252 oid2 = _write_obj(repo, b"v2")
253 _commit(repo, "c2", {"f.py": oid2}, parent=c1)
254 full = _json_out(_pack(repo, "HEAD", "--dry-run"))
255 pruned = _json_out(_pack(repo, "HEAD", "--have", c1, "--dry-run"))
256 assert pruned["objects"] < full["objects"]
257
258
259 # ---------------------------------------------------------------------------
260 # unpack-objects: duration_ms and exit_code [RED]
261 # ---------------------------------------------------------------------------
262
263 class TestUnpackObjectsSupercharge:
264 """[RED] duration_ms and exit_code in unpack-objects JSON output."""
265
266 def test_unpack_json_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
267 repo = _make_repo(tmp_path)
268 bundle = _make_bundle(repo)
269 dest = _make_repo(tmp_path / "dest")
270 r = _unpack(dest, bundle)
271 assert r.exit_code == 0
272 d = _json_out(r)
273 assert "duration_ms" in d
274
275 def test_unpack_duration_ms_non_negative(self, tmp_path: pathlib.Path) -> None:
276 repo = _make_repo(tmp_path)
277 bundle = _make_bundle(repo)
278 dest = _make_repo(tmp_path / "dest")
279 d = _json_out(_unpack(dest, bundle))
280 assert d["duration_ms"] >= 0.0
281
282 def test_unpack_json_has_exit_code(self, tmp_path: pathlib.Path) -> None:
283 repo = _make_repo(tmp_path)
284 bundle = _make_bundle(repo)
285 dest = _make_repo(tmp_path / "dest")
286 d = _json_out(_unpack(dest, bundle))
287 assert "exit_code" in d
288
289 def test_unpack_exit_code_zero_on_success(self, tmp_path: pathlib.Path) -> None:
290 repo = _make_repo(tmp_path)
291 bundle = _make_bundle(repo)
292 dest = _make_repo(tmp_path / "dest")
293 d = _json_out(_unpack(dest, bundle))
294 assert d["exit_code"] == 0
295
296 def test_unpack_duration_ms_under_two_seconds(self, tmp_path: pathlib.Path) -> None:
297 repo = _make_repo(tmp_path)
298 for i in range(20):
299 oid = _write_obj(repo, f"content {i}".encode() * 50)
300 _commit(repo, f"c{i}", {f"f{i}.py": oid},
301 parent=None if i == 0 else None) # single chain not needed for pack
302 bundle = _make_bundle(repo)
303 dest = _make_repo(tmp_path / "dest")
304 d = _json_out(_unpack(dest, bundle))
305 assert d["duration_ms"] < 2000.0
306
307
308 # ---------------------------------------------------------------------------
309 # unpack-objects: existing output fields still present [GREEN]
310 # ---------------------------------------------------------------------------
311
312 class TestUnpackObjectsGreen:
313 def test_commits_written_field(self, tmp_path: pathlib.Path) -> None:
314 repo = _make_repo(tmp_path)
315 bundle = _make_bundle(repo)
316 dest = _make_repo(tmp_path / "dest")
317 d = _json_out(_unpack(dest, bundle))
318 assert "commits_written" in d
319
320 def test_snapshots_written_field(self, tmp_path: pathlib.Path) -> None:
321 repo = _make_repo(tmp_path)
322 bundle = _make_bundle(repo)
323 dest = _make_repo(tmp_path / "dest")
324 d = _json_out(_unpack(dest, bundle))
325 assert "snapshots_written" in d
326
327 def test_objects_written_field(self, tmp_path: pathlib.Path) -> None:
328 repo = _make_repo(tmp_path)
329 bundle = _make_bundle(repo)
330 dest = _make_repo(tmp_path / "dest")
331 d = _json_out(_unpack(dest, bundle))
332 assert "objects_written" in d
333
334 def test_objects_skipped_field(self, tmp_path: pathlib.Path) -> None:
335 repo = _make_repo(tmp_path)
336 bundle = _make_bundle(repo)
337 dest = _make_repo(tmp_path / "dest")
338 d = _json_out(_unpack(dest, bundle))
339 assert "objects_skipped" in d
340
341 def test_idempotent_second_unpack_skips_all(self, tmp_path: pathlib.Path) -> None:
342 repo = _make_repo(tmp_path)
343 bundle = _make_bundle(repo)
344 dest = _make_repo(tmp_path / "dest")
345 _unpack(dest, bundle)
346 d = _json_out(_unpack(dest, bundle))
347 assert d["objects_written"] == 0
348
349
350 # ---------------------------------------------------------------------------
351 # verify-pack: duration_ms and exit_code [RED]
352 # ---------------------------------------------------------------------------
353
354 class TestVerifyPackSupercharge:
355 """[RED] duration_ms and exit_code in verify-pack JSON output."""
356
357 def test_verify_json_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
358 repo = _make_repo(tmp_path)
359 bundle = _make_bundle(repo)
360 r = _verify(repo, bundle)
361 assert r.exit_code == 0
362 d = _json_out(r)
363 assert "duration_ms" in d
364
365 def test_verify_duration_ms_non_negative(self, tmp_path: pathlib.Path) -> None:
366 repo = _make_repo(tmp_path)
367 bundle = _make_bundle(repo)
368 d = _json_out(_verify(repo, bundle))
369 assert d["duration_ms"] >= 0.0
370
371 def test_verify_json_has_exit_code(self, tmp_path: pathlib.Path) -> None:
372 repo = _make_repo(tmp_path)
373 bundle = _make_bundle(repo)
374 d = _json_out(_verify(repo, bundle))
375 assert "exit_code" in d
376
377 def test_verify_exit_code_zero_on_clean(self, tmp_path: pathlib.Path) -> None:
378 repo = _make_repo(tmp_path)
379 bundle = _make_bundle(repo)
380 d = _json_out(_verify(repo, bundle))
381 assert d["exit_code"] == 0
382
383 def test_verify_exit_code_nonzero_on_corrupt(self, tmp_path: pathlib.Path) -> None:
384 repo = _make_repo(tmp_path)
385 bundle = _make_bundle(repo)
386 # Corrupt the bundle: flip a byte in the middle
387 corrupted = bytearray(bundle)
388 corrupted[len(corrupted) // 2] ^= 0xFF
389 r = _verify(repo, bytes(corrupted))
390 # Either fails to parse or reports integrity failure
391 assert r.exit_code != 0 or not _json_out(r).get("all_ok", True)
392
393 def test_verify_stat_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
394 repo = _make_repo(tmp_path)
395 bundle = _make_bundle(repo)
396 r = _verify(repo, bundle, "--stat")
397 assert r.exit_code == 0
398 d = _json_out(r)
399 assert "duration_ms" in d
400
401 def test_verify_stat_has_exit_code(self, tmp_path: pathlib.Path) -> None:
402 repo = _make_repo(tmp_path)
403 bundle = _make_bundle(repo)
404 d = _json_out(_verify(repo, bundle, "--stat"))
405 assert "exit_code" in d
406
407 def test_verify_duration_ms_under_two_seconds(self, tmp_path: pathlib.Path) -> None:
408 repo = _make_repo(tmp_path)
409 for i in range(50):
410 _write_obj(repo, f"obj {i}".encode() * 100)
411 bundle = _make_bundle(repo)
412 d = _json_out(_verify(repo, bundle))
413 assert d["duration_ms"] < 2000.0
414
415
416 # ---------------------------------------------------------------------------
417 # verify-pack: existing fields still present [GREEN]
418 # ---------------------------------------------------------------------------
419
420 class TestVerifyPackGreen:
421 def test_all_ok_field_clean_bundle(self, tmp_path: pathlib.Path) -> None:
422 repo = _make_repo(tmp_path)
423 bundle = _make_bundle(repo)
424 d = _json_out(_verify(repo, bundle))
425 assert d["all_ok"] is True
426
427 def test_failures_empty_on_clean_bundle(self, tmp_path: pathlib.Path) -> None:
428 repo = _make_repo(tmp_path)
429 bundle = _make_bundle(repo)
430 d = _json_out(_verify(repo, bundle))
431 assert d["failures"] == []
432
433 def test_objects_checked_field(self, tmp_path: pathlib.Path) -> None:
434 repo = _make_repo(tmp_path)
435 bundle = _make_bundle(repo)
436 d = _json_out(_verify(repo, bundle))
437 assert "objects_checked" in d
438
439 def test_stat_objects_count(self, tmp_path: pathlib.Path) -> None:
440 repo = _make_repo(tmp_path)
441 oid_a = _write_obj(repo, b"a")
442 oid_b = _write_obj(repo, b"b")
443 _commit(repo, "init", {"a.py": oid_a, "b.py": oid_b})
444 bundle = _pack(repo, "HEAD").stdout_bytes
445 d = _json_out(_verify(repo, bundle, "--stat"))
446 assert d["objects"] >= 2
447
448 def test_stat_commits_count(self, tmp_path: pathlib.Path) -> None:
449 repo = _make_repo(tmp_path)
450 bundle = _make_bundle(repo)
451 d = _json_out(_verify(repo, bundle, "--stat"))
452 assert d["commits"] >= 1
453
454
455 # ---------------------------------------------------------------------------
456 # Phase 3 — build_mpack fails loudly on MISSING objects [RED]
457 # ---------------------------------------------------------------------------
458
459 def _write_promisor_config(repo: pathlib.Path, remote_name: str = "origin") -> None:
460 config_path = repo / ".muse" / "config.toml"
461 config_path.write_text(
462 f"[remotes.{remote_name}]\n"
463 f'url = "http://localhost:10003/test/repo"\n',
464 encoding="utf-8",
465 )
466
467
468 class TestPackObjectsMissingObjectValidation:
469 """pack-objects fails loudly when a snapshot references a MISSING object."""
470
471 def test_missing_object_exits_nonzero(self, tmp_path: pathlib.Path) -> None:
472 """pack-objects exits nonzero when a snapshot refs an object absent with no promisor."""
473 repo = _make_repo(tmp_path)
474 # Write snapshot that refs an object we deliberately do NOT write
475 from muse.core.snapshot import compute_snapshot_id
476 from muse.core.store import SnapshotRecord, write_snapshot, CommitRecord, write_commit
477 missing_oid = long_id("a" * 64)
478 sid = compute_snapshot_id({"missing.py": missing_oid})
479 write_snapshot(repo, SnapshotRecord(snapshot_id=sid, manifest={"missing.py": missing_oid}, created_at=_TS))
480 cid = _commit.__wrapped__(repo, "broken commit", {"missing.py": missing_oid}) if hasattr(_commit, "__wrapped__") else None
481 # Use the helpers directly
482 from muse.core.snapshot import compute_commit_id
483 cid = compute_commit_id([], sid, "broken commit", _TS.isoformat())
484 write_commit(repo, CommitRecord(
485 commit_id=cid, repo_id="test-repo", branch="main",
486 snapshot_id=sid, message="broken commit", committed_at=_TS,
487 author="gabriel", parent_commit_id=None, parent2_commit_id=None,
488 ))
489 (repo / ".muse" / "refs" / "heads" / "main").write_text(cid)
490 r = _pack(repo, "HEAD")
491 assert r.exit_code != 0
492
493 def test_missing_object_error_mentions_object_id(self, tmp_path: pathlib.Path) -> None:
494 """Error output names the missing object so the user knows what to fix."""
495 repo = _make_repo(tmp_path)
496 missing_oid = long_id("b" * 64)
497 from muse.core.snapshot import compute_snapshot_id, compute_commit_id
498 from muse.core.store import SnapshotRecord, write_snapshot, CommitRecord, write_commit
499 sid = compute_snapshot_id({"gone.py": missing_oid})
500 write_snapshot(repo, SnapshotRecord(snapshot_id=sid, manifest={"gone.py": missing_oid}, created_at=_TS))
501 cid = compute_commit_id([], sid, "gone", _TS.isoformat())
502 write_commit(repo, CommitRecord(
503 commit_id=cid, repo_id="test-repo", branch="main",
504 snapshot_id=sid, message="gone", committed_at=_TS,
505 author="gabriel", parent_commit_id=None, parent2_commit_id=None,
506 ))
507 (repo / ".muse" / "refs" / "heads" / "main").write_text(cid)
508 r = _pack(repo, "HEAD")
509 assert r.exit_code != 0
510 # Error should mention the missing object or "missing"
511 assert "missing" in (r.output + r.stderr).lower() or "absent" in (r.output + r.stderr).lower()
512
513 def test_promised_object_does_not_fail(self, tmp_path: pathlib.Path) -> None:
514 """PROMISED objects (promisor remote configured) are skipped, not failures."""
515 repo = _make_repo(tmp_path)
516 _write_promisor_config(repo)
517 missing_oid = long_id("c" * 64)
518 from muse.core.snapshot import compute_snapshot_id, compute_commit_id
519 from muse.core.store import SnapshotRecord, write_snapshot, CommitRecord, write_commit
520 sid = compute_snapshot_id({"remote.py": missing_oid})
521 write_snapshot(repo, SnapshotRecord(snapshot_id=sid, manifest={"remote.py": missing_oid}, created_at=_TS))
522 cid = compute_commit_id([], sid, "partial clone", _TS.isoformat())
523 write_commit(repo, CommitRecord(
524 commit_id=cid, repo_id="test-repo", branch="main",
525 snapshot_id=sid, message="partial clone", committed_at=_TS,
526 author="gabriel", parent_commit_id=None, parent2_commit_id=None,
527 ))
528 (repo / ".muse" / "refs" / "heads" / "main").write_text(cid)
529 r = _pack(repo, "HEAD")
530 assert r.exit_code == 0
531
532 def test_present_object_always_passes(self, tmp_path: pathlib.Path) -> None:
533 """Fully self-contained bundle with all objects present passes."""
534 repo = _make_repo(tmp_path)
535 oid = _write_obj(repo, b"complete content")
536 _commit(repo, "good", {"file.py": oid})
537 r = _pack(repo, "HEAD")
538 assert r.exit_code == 0
File History 1 commit
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 138 days ago