Skip to content

Commit 8a884a6

Browse files
codexByron
authored andcommitted
feat(gix): discover untracked files natively
Use GixPython's directory walk for `Repo.untracked_files`, preserving raw names, nested-repository directory markers and sorting. Keep extra status options, unsupported index storage and non-root command working directories on the existing CLI path. The staged version passes Ruff and Python syntax checks. Its SHA-1/SHA-256 native smoke and focused pytest selection pass: 28 passed in 7.17s.
1 parent b2f6e96 commit 8a884a6

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

‎git/_backend.py‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,53 @@ def describe(local: Any) -> List[bytes]:
244244
return b"".join(records)
245245

246246

247+
def _worktree_root(command: Any, repo: Any) -> str:
248+
root = repo.workdir()
249+
if (
250+
root is None
251+
or repo.prefix() is not None
252+
or os.path.realpath(command.working_dir or os.getcwd()) != os.path.realpath(root)
253+
):
254+
raise _Unsupported("worktree or command working directory")
255+
return os.fspath(root)
256+
257+
258+
def untracked_files(command: Any, args: Tuple[Any, ...], options: Dict[str, Any]) -> Any:
259+
if gix is None:
260+
return NotImplemented
261+
method = "Repo.untracked_files"
262+
command._require_version()
263+
try:
264+
if options.keys() - {"ignore_submodules"}:
265+
raise _Unsupported("status options")
266+
repo = _repository(command, {})
267+
_worktree_root(command, repo)
268+
if not hasattr(repo, "dirwalk_iter"):
269+
raise _Unsupported("dirwalk feature disabled")
270+
index = _index(repo, {"env": command.environment()})
271+
patterns = command._unpack_args([arg for arg in args if arg is not None])
272+
if any("\0" in path for path in patterns):
273+
raise _Unsupported("invalid pathspec")
274+
walk_options = repo.dirwalk_options().emit_untracked("matching").emit_tracked(False)
275+
result = []
276+
with repo.dirwalk_iter(index, patterns, walk_options) as entries:
277+
for item in entries:
278+
entry = item.entry
279+
if entry.status == "Untracked":
280+
path = entry.rela_path
281+
if entry.disk_kind in ("Directory", "Repository"):
282+
path += b"/"
283+
result.append(path)
284+
result.sort()
285+
except _Unsupported as exc:
286+
return _fallback(method, str(exc))
287+
except gix.Error as exc:
288+
_logger.debug("untracked_files native read: %s", exc)
289+
return _fallback(method, "native read diagnostics")
290+
record(method, "native")
291+
return [safe_decode(path) for path in result]
292+
293+
247294
def _merge_base(repo: Any, args: List[str], kwargs: Dict[str, Any]) -> bytes:
248295
if not hasattr(repo, "merge_base"):
249296
raise _Unsupported("revision feature disabled")

‎git/repo/base.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from gitdb.db.loose import LooseObjectDB
2323
from gitdb.exc import BadObject
2424

25+
from git import _backend
2526
from git.cmd import Git, handle_process_output
2627
from git.compat import defenc, safe_decode
2728
from git.config import GitConfigParser
@@ -1136,6 +1137,9 @@ def untracked_files(self) -> List[str]:
11361137
return self._get_untracked_files()
11371138

11381139
def _get_untracked_files(self, *args: Any, **kwargs: Any) -> List[str]:
1140+
native = _backend.untracked_files(self.git, args, kwargs)
1141+
if native is not NotImplemented:
1142+
return native
11391143
# NUL records preserve arbitrary filenames, including newlines and quotes.
11401144
output = self.git._call_process_safe(
11411145
"status", "--porcelain=v1", "-z", "--untracked-files=all", "--", *args, stdout_as_string=False, **kwargs

0 commit comments

Comments
 (0)