Skip to content

Commit cd315d7

Browse files
codexByron
authored andcommitted
feat(gix): check ignored paths natively
Use native exclude matching for `Repo.ignored`, retaining tracked-file suppression, directory modes and the caller's path spelling. Preserve CLI diagnostics for unsupported normalization and paths traversing symlinks or tracked gitlinks. 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.03s.
1 parent d61cf76 commit cd315d7

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

‎git/_backend.py‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
import logging
1414
import os
1515
import re
16+
import stat
1617
from threading import Lock
17-
from typing import Any, Callable, Dict, Iterator, List, Sequence, Tuple, cast
18+
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Tuple, cast
1819

1920
from git.compat import safe_decode
2021
from git.exc import GitCommandError
@@ -291,6 +292,53 @@ def untracked_files(command: Any, args: Tuple[Any, ...], options: Dict[str, Any]
291292
return [safe_decode(path) for path in result]
292293

293294

295+
def ignored(command: Any, paths: Sequence[Any]) -> Any:
296+
if gix is None:
297+
return NotImplemented
298+
method = "Repo.ignored"
299+
command._require_version()
300+
try:
301+
repo = _repository(command, {})
302+
root = _worktree_root(command, repo)
303+
if not hasattr(repo, "excludes"):
304+
raise _Unsupported("excludes feature disabled")
305+
index = _index(repo, {"env": command.environment()})
306+
excludes = repo.excludes(index)
307+
with index.entries() as entries:
308+
tracked = {entry.path() for entry in entries}
309+
result = []
310+
for path in paths:
311+
path = os.fspath(path)
312+
relative = os.path.relpath(path, root) if os.path.isabs(path) else path
313+
relative = relative.replace(os.sep, "/")
314+
if any(part in (".", "..", ".git") for part in relative.split("/")) or relative.startswith(":"):
315+
raise _Unsupported("ignore path normalization")
316+
# check-ignore rejects traversal through symlinks and tracked gitlinks.
317+
parent = relative.rstrip("/")
318+
while "/" in parent:
319+
parent = parent.rsplit("/", 1)[0]
320+
if os.path.islink(os.path.join(root, parent)) or os.fsencode(parent) in tracked:
321+
raise _Unsupported("ignore path traverses symlink or submodule")
322+
if os.fsencode(relative.rstrip("/")) in tracked:
323+
continue
324+
mode: Optional[int]
325+
try:
326+
mode = os.lstat(os.path.join(root, relative)).st_mode
327+
except FileNotFoundError:
328+
mode = stat.S_IFDIR if relative.endswith("/") else None
329+
else:
330+
mode = 0o40000 if stat.S_ISDIR(mode) else 0o120000 if stat.S_ISLNK(mode) else 0o100644
331+
if excludes.at_entry(os.fsencode(relative), mode).is_excluded():
332+
result.append(path)
333+
except _Unsupported as exc:
334+
return _fallback(method, str(exc))
335+
except gix.Error as exc:
336+
_logger.debug("ignored native read: %s", exc)
337+
return _fallback(method, "native read diagnostics")
338+
record(method, "native")
339+
return result
340+
341+
294342
def _merge_base(repo: Any, args: List[str], kwargs: Dict[str, Any]) -> bytes:
295343
if not hasattr(repo, "merge_base"):
296344
raise _Unsupported("revision feature disabled")

‎git/repo/base.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,9 @@ def ignored(self, *paths: PathLike) -> List[str]:
11611161
for path in paths:
11621162
if "\0" in os.fspath(path):
11631163
raise ValueError("Paths cannot contain NUL")
1164+
native = _backend.ignored(self.git, paths)
1165+
if native is not NotImplemented:
1166+
return native
11641167
with tempfile.TemporaryFile() as stream:
11651168
stream.write(b"\0".join(os.fspath(path).encode(defenc, "surrogateescape") for path in paths) + b"\0")
11661169
stream.seek(0)

0 commit comments

Comments
 (0)