Skip to content

Commit fd63919

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 143df53 commit fd63919

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
@@ -440,6 +440,28 @@ def write_tree(command: Any, entries: Sequence[Tuple[bytes, int, str]]) -> Any:
440440
return result
441441

442442

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

‎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
@@ -86,6 +87,18 @@ def handler(native, args, kwargs):
8687
assert attempts == ["write"]
8788

8889

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

0 commit comments

Comments
 (0)