Skip to content

Commit 389acfc

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 bfb24b8 commit 389acfc

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
@@ -248,6 +248,53 @@ def describe(local: Any) -> List[bytes]:
248248
return b"".join(records)
249249

250250

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