Skip to content

Commit 849bf59

Browse files
codexByron
authored andcommitted
feat(gix): serialize trees with native object storage
Handle GitPython's NUL-delimited `mktree --missing` input with the shared native tree editor. Validate entry kinds and paths before writing, preserve raw filenames, and keep missing-child cases on Git until the native editor can represent them (GIX-7). The staged version passes Ruff and Python syntax checks. Its SHA-1/SHA-256 native smoke and focused pytest selection pass: 34 passed in 5.13s.
1 parent c1a5677 commit 849bf59

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

‎git/_backend.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,28 @@ def write_tree(command: Any, entries: Sequence[Tuple[bytes, int, str]]) -> Any:
444444
return result
445445

446446

447+
def _mktree(repo: Any, args: List[str], kwargs: Dict[str, Any]) -> bytes:
448+
if args != ["-z", "--missing"]:
449+
raise _Unsupported("tree writing options")
450+
data = _input(kwargs)
451+
entries = []
452+
for record in data.split(b"\0"):
453+
if not record:
454+
continue
455+
metadata, path = record.split(b"\t", 1)
456+
mode_bytes, kind, oid_bytes = metadata.split()
457+
mode = int(mode_bytes, 8)
458+
if mode not in _ENTRY_KINDS or b"/" in path or path in (b"", b".", b"..", b".git"):
459+
raise _Unsupported("tree entry")
460+
expected = b"tree" if mode == 0o40000 else b"commit" if mode == 0o160000 else b"blob"
461+
if kind != expected:
462+
raise _Unsupported("tree entry kind")
463+
entries.append((path, mode, oid_bytes.decode("ascii")))
464+
oid = _write_tree(repo, entries)
465+
kwargs["istream"].seek(len(data), 1)
466+
return oid.encode("ascii") + b"\n"
467+
468+
447469
_HANDLERS: Dict[str, Callable[[Any, List[str], Dict[str, Any]], bytes]] = {
448470
"rev_parse": _rev_parse,
449471
"ls_tree": _ls_tree,
@@ -453,6 +475,7 @@ def write_tree(command: Any, entries: Sequence[Tuple[bytes, int, str]]) -> Any:
453475
"ls_files": _ls_files,
454476
"update_index": _update_index,
455477
"hash_object": _hash_object,
478+
"mktree": _mktree,
456479
"config": _config,
457480
"worktree": _worktree,
458481
}

‎test/test_gix_backend.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from io import BytesIO
44
from pathlib import Path
5+
import tempfile
56
from unittest.mock import patch
67

78
import pytest
@@ -87,6 +88,18 @@ def handler(native, args, kwargs):
8788
assert attempts == ["write"]
8889

8990

91+
@pytest.mark.parametrize("mode,kind", [("100644", "blob"), ("040000", "tree"), ("160000", "commit")])
92+
def test_tree_writes_validate_child_object_kinds(repo, mode, kind):
93+
commit = repo.index.commit("initial", skip_hooks=True)
94+
blob = repo.odb.store(IStream("blob", 1, BytesIO(b"x")))
95+
wrong_id = blob.hexsha.decode() if kind == "commit" else commit.hexsha
96+
with tempfile.TemporaryFile() as stream:
97+
stream.write((mode + " " + kind + " " + wrong_id + "\twrong\0").encode())
98+
stream.seek(0)
99+
with pytest.raises(GitCommandError):
100+
repo.git._call_process_safe("mktree", "-z", "--missing", istream=stream)
101+
102+
90103
def test_partial_native_stream_does_not_corrupt_next_read(repo):
91104
one = repo.odb.store(IStream("blob", 6, BytesIO(b"abcdef"))).binsha
92105
two = repo.odb.store(IStream("blob", 3, BytesIO(b"xyz"))).binsha

0 commit comments

Comments
 (0)