Skip to content

Commit fc02a24

Browse files
codexByron
authored andcommitted
feat(gix): resolve revisions and repository metadata natively
Handle verified revisions and repository metadata exposed by GixPython. Keep Git's initial repository validation and unsupported discovery options. Message searches, dirty suffixes and describe-shaped names fall back because the native grammar differs in regex handling and exact-tag precedence (`GIX-14`/`GIX-17`). GixPython's native configuration identifies `extensions.refStorage`; accessing `HEAD` in either a SHA-1 or SHA-256 reftable repository raises `gix.Error` with an unsupported-storage cause. The bindings currently flatten the native error kind. Add a direct regression for this signal, without manufacturing an `Unsupported` exception in Python. Keep `rev-parse --show-ref-format` on Git because it also validates repository extensions. Even with strict configuration and trust options, Gix accepts an unknown version-1 extension and successfully reads `HEAD` where Git rejects the repository. Add that regression too. A reference-format getter is not the prerequisite: compatible extension validation is. The remaining query costs one CLI invocation after wrapper initialization; this amendment does not reduce it. Validation: affected backend and revision tests passed with Gix first (30 passed in 20.22s), then CLI (22 passed, 1 skipped in 21.26s). Ruff lint/format and `git diff --check` passed.
1 parent e005af3 commit fc02a24

2 files changed

Lines changed: 47 additions & 2 deletions

File tree

‎git/_backend.py‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,29 @@ def object_data(command: Any, ref: bytes, *, stream: bool = False) -> Any:
124124
return result
125125

126126

127-
_HANDLERS: Dict[str, Callable[[Any, List[str], Dict[str, Any]], bytes]] = {}
127+
def _rev_parse(repo: Any, args: List[str], kwargs: Dict[str, Any]) -> bytes:
128+
if args == ["--path-format=absolute", "--git-common-dir"]:
129+
return os.fsencode(os.path.abspath(repo.common_dir())) + b"\n"
130+
if args == ["--is-bare-repository"]:
131+
return b"true\n" if repo.is_bare() else b"false\n"
132+
if args == ["--show-object-format"]:
133+
return str(repo.object_hash()).encode("ascii") + b"\n"
134+
if args == ["--show-ref-format"]:
135+
# Config identifies reftable and HEAD reports it as unsupported, but
136+
# neither validates unknown repository extensions as Git's query does.
137+
raise _Unsupported("reference storage format query (GIX-1)")
138+
if args == ["--show-toplevel"] and repo.workdir() is not None:
139+
return os.fsencode(os.path.abspath(repo.workdir())) + b"\n"
140+
if args[:1] != ["--verify"] or args[-2:-1] != ["--end-of-options"]:
141+
raise _Unsupported("discovery or revision options")
142+
if args[:-2] not in (["--verify"], ["--verify", "--quiet"]):
143+
raise _Unsupported("revision options")
144+
return str(_oid(repo, args[-1])).encode("ascii") + b"\n"
145+
146+
147+
_HANDLERS: Dict[str, Callable[[Any, List[str], Dict[str, Any]], bytes]] = {
148+
"rev_parse": _rev_parse,
149+
}
128150

129151

130152
def dispatch(

‎test/test_gix_backend.py‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import pytest
88

99
from git import Git, Repo, _backend
10+
from git.exc import InvalidGitRepositoryError
1011
from gitdb import IStream
1112

1213
gix = pytest.importorskip("gix")
@@ -18,6 +19,27 @@ def repo(request, tmp_path):
1819
yield repo
1920

2021

22+
def test_reftable_head_reports_unsupported_storage(repo, tmp_path):
23+
with Repo.init(tmp_path / "reftable", object_format=repo.object_format, ref_format="reftable") as reftable:
24+
options = gix.OpenOptions().open_path_as_is(True).bail_if_untrusted(True).strict_config(True)
25+
native = gix.open_opts(reftable.git_dir, options)
26+
assert native.config_snapshot().string("extensions.refStorage") == b"reftable"
27+
# The Rust Unsupported cause is flattened into gix.Error by the bindings.
28+
with pytest.raises(gix.Error, match="unsupported storage backend"):
29+
native.head()
30+
assert reftable.ref_format == "reftable"
31+
32+
33+
def test_native_head_does_not_validate_repository_extensions(repo):
34+
repo.git.config("core.repositoryFormatVersion", "1")
35+
repo.git.config("extensions.unknown", "true")
36+
options = gix.OpenOptions().open_path_as_is(True).bail_if_untrusted(True).strict_config(True)
37+
assert gix.open_opts(repo.git_dir, options).head().is_unborn()
38+
# Git's format query also validates extensions; HEAD alone cannot replace it.
39+
with pytest.raises(InvalidGitRepositoryError):
40+
Repo(repo.git_dir)
41+
42+
2143
def test_unknown_command_and_storage_environment_use_cli(repo):
2244
before = _backend.statistics()
2345
assert repo.git._call_process_safe("status", "--porcelain") == ""
@@ -30,7 +52,8 @@ def test_unknown_command_and_storage_environment_use_cli(repo):
3052
assert repo.git._call_process_safe("for_each_ref", "--format=%(refname)", "--", "refs/*") == "fallback"
3153
with repo.git.custom_environment(GIT_INDEX_FILE=".git/index"):
3254
assert repo.git._call_process_safe("ls_files", "--stage", "-v", "-z", "--full-name") == "fallback"
33-
assert cli.call_count == 2
55+
assert repo.git._call_process_safe("rev_parse", "--show-ref-format") == "fallback"
56+
assert cli.call_count == 3
3457

3558

3659
def test_partial_native_stream_does_not_corrupt_next_read(repo):

0 commit comments

Comments
 (0)