Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions crab/docs/architecture/indexed-remote-reads.md
Original file line number Diff line number Diff line change
Expand Up @@ -2154,3 +2154,27 @@ the remote tip, revoke old-tip visibility, install packs into an independent
repository and run Git connectivity checks. The existing RustFS ref matrix
also exercises native and remote-helper force pushes without fetching first.
These tests remain pending until the updated-source CI run completes.

## Packed-object qualification fault fixture

The Git-current protocol canary stopped before corruption injection because the
LFS pointer blob was packed, while the fixture opened a loose-object pathname.
The same failure reproduces locally after `git repack -ad`.

The fixture now exports its small disposable source history and unpacks it into
an empty object database before injection. Healthy packed duplicates cannot
mask the corrupt loose blob. The original object database is moved aside and
restored in `finally`; the success check compares every original object-file
hash, the pointer, source/remote refs and working bytes. The caller deliberately
packs its source first so the previous failure remains a regression case.
The checksum mismatch and successful restored dry run remain required.

This changes qualification setup only. Full Git-current CI remains required;
a source-layout repair does not establish a product-level compatibility result.

Local proof with the unchanged #572 release: the repaired packed-source mirror
workflow passes 66 commands and 13 checks; deliberate assertion failure after
the verified checksum rejection restores every original object-file hash and
passes strict Git fsck (34 commands, three checks; cleanup scope only). Seventeen
existing log/source-isolation tests pass. These are selected fixture and mirror
receipts, not a full final-source protocol qualification.
57 changes: 43 additions & 14 deletions crab/scripts/e2e/run_protocol_v2_partial_clone_rustfs_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -2835,19 +2835,41 @@ def mirror_lfs_discovery_check(self, source: Path, destination: str) -> None:
oid = self.git_value(source, ["rev-parse", "HEAD:hook-lfs.bin"], name="capture LFS pointer Git OID")
head = self.git_value(source, ["rev-parse", "HEAD"], name="capture LFS discovery source ref")
before = self.git_value(source, ["ls-remote", "--refs", destination], name="capture refs before LFS discovery fault")
path = source / ".git/objects" / oid[:2] / oid[2:]
original = path.read_bytes()
pointer = self.git_value(source, ["cat-file", "blob", oid], name="capture healthy LFS pointer")
working_bytes = (source / "hook-lfs.bin").read_bytes()
large_file = self.artifacts / "lfs-discovery-large-blob.bin"
large_file.write_bytes(b"x" * 65536)
large_oid = self.git_value(source, ["hash-object", "-w", str(large_file)], name="create LFS discovery large-header fault")
replacement = (source / ".git/objects" / large_oid[:2] / large_oid[2:]).read_bytes()
command = [str(self.crab_bin), "lfs", "push", "--dry-run", destination]
# Only this disposable fixture's directory entry changes. Restore it
# even if the assertion fails; no source worktree data is rewritten.
path.unlink()
objects = source / ".git/objects"
original_objects = {
str(path.relative_to(objects)): sha256_file(path)
for path in objects.rglob("*") if path.is_file()
}
saved_objects = source / ".git/lfs-discovery-saved-objects"
loose_objects = source / ".git/lfs-discovery-loose-objects"
loose_objects.mkdir()
pack = self.artifacts / "lfs-discovery-source.pack"
self.run_binary(
"export disposable LFS source objects",
[str(self.git_bin), "pack-objects", "--all", "--stdout"], source, pack,
)
# Unpack into an empty database: Git skips objects already present, and
# a healthy packed duplicate could mask the corrupt loose entry in gix.
self.run_git(
source, ["unpack-objects", "--strict"],
name="prepare isolated loose LFS fault database",
extra_env={"GIT_OBJECT_DIRECTORY": str(loose_objects), "GIT_ALTERNATE_OBJECT_DIRECTORIES": ""},
input_data=pack.read_bytes(),
)
make_private_fixture_tree(loose_objects)
objects.rename(saved_objects)
try:
loose_objects.rename(objects)
path = objects / oid[:2] / oid[2:]
large_file = self.artifacts / "lfs-discovery-large-blob.bin"
large_file.write_bytes(b"x" * 65536)
large_oid = self.git_value(source, ["hash-object", "-w", str(large_file)], name="create LFS discovery large-header fault")
replacement = (objects / large_oid[:2] / large_oid[2:]).read_bytes()
path.unlink()
path.write_bytes(replacement)
command = [str(self.crab_bin), "lfs", "push", "--dry-run", destination]
rejected = self.run_cmd("LFS discovery rejects oversized corrupt pointer", command, source, check=False)
self.check(
"mirror-lfs-discovery-rejects-corrupt-git-pointer",
Expand All @@ -2857,13 +2879,19 @@ def mirror_lfs_discovery_check(self, source: Path, destination: str) -> None:
and self.git_value(source, ["ls-remote", "--refs", destination], name="verify remote refs after LFS discovery refusal") == before,
)
finally:
if path.exists():
path.unlink()
path.write_bytes(original)
# Restore the original object database even when injection or its
# assertion fails; packed/loose layout and original bytes stay intact.
if objects.exists():
shutil.rmtree(objects)
saved_objects.rename(objects)
self.run_cmd("LFS discovery accepts restored Git pointer", command, source)
self.check(
"mirror-lfs-discovery-restoration-preserves-refs",
path.read_bytes() == original and (source / "hook-lfs.bin").read_bytes() == working_bytes
{str(path.relative_to(objects)): sha256_file(path)
for path in objects.rglob("*") if path.is_file()} == original_objects
and self.git_value(source, ["cat-file", "blob", oid], name="verify restored LFS pointer") == pointer
and self.git_value(source, ["rev-parse", "HEAD"], name="verify restored LFS source ref") == head
and (source / "hook-lfs.bin").read_bytes() == working_bytes
and self.git_value(source, ["ls-remote", "--refs", destination], name="verify refs after LFS discovery restoration") == before,
)

Expand Down Expand Up @@ -2920,6 +2948,7 @@ def mirror_pre_push_batch_checks(self) -> None:
)
self.run_git(source, ["add", ".gitattributes", "hook-lfs.bin"])
self.run_git(source, ["commit", "-m", "hook LFS data"])
self.run_git(source, ["repack", "-ad"], name="pack hook source before LFS discovery fault")
self.mirror_lfs_discovery_check(source, destination)
self.run_git(source, ["tag", "-a", "hook-v1", "-m", "hook annotated tag"])
selected = self.git_value(source, ["rev-parse", "topic"], name="hook selected commit")
Expand Down
Loading