You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
118 lines
2.9 KiB
Python
118 lines
2.9 KiB
Python
import os
|
|
from pathlib import Path
|
|
|
|
import pathspec
|
|
|
|
|
|
def find_project_root(start_path):
|
|
"""
|
|
Traverse up from a starting path to find the project root.
|
|
|
|
The project root is identified by the presence of a '.git' directory inside it.
|
|
"""
|
|
current_path = Path(start_path)
|
|
|
|
while current_path != current_path.root:
|
|
if (current_path / ".git").is_dir():
|
|
return str(current_path)
|
|
|
|
if (current_path / ".hg").is_dir():
|
|
return current_path
|
|
|
|
if (current_path / "pyproject.toml").is_file():
|
|
return current_path
|
|
|
|
if (current_path / "setup.py").is_file():
|
|
return current_path
|
|
|
|
current_path = current_path.parent
|
|
|
|
return None
|
|
|
|
|
|
ALWAYS_IGNORE = """
|
|
.git
|
|
__pycache__
|
|
.direnv
|
|
.eggs
|
|
.git
|
|
.hg
|
|
.mypy_cache
|
|
.nox
|
|
.tox
|
|
.venv
|
|
venv
|
|
.svn
|
|
.ipynb_checkpoints
|
|
_build
|
|
buck-out
|
|
build
|
|
dist
|
|
__pypackages__
|
|
"""
|
|
|
|
|
|
def load_gitignore_spec_at_path(path):
|
|
gitignore_path = os.path.join(path, ".gitignore")
|
|
|
|
if os.path.exists(gitignore_path):
|
|
with open(gitignore_path, encoding="utf-8") as f:
|
|
patterns = f.read().split("\n")
|
|
patterns.extend(ALWAYS_IGNORE.split("\n"))
|
|
ignore_spec = pathspec.PathSpec.from_lines("gitwildmatch", patterns)
|
|
else:
|
|
ignore_spec = pathspec.PathSpec.from_lines("gitwildmatch", [])
|
|
return ignore_spec
|
|
|
|
|
|
def get_nonignored_file_paths(directory, gitignore_dict=None, extensions=()):
|
|
return_relative = False
|
|
if gitignore_dict is None:
|
|
gitignore_dict = {}
|
|
return_relative = True
|
|
gitignore_dict = {
|
|
**gitignore_dict,
|
|
directory: load_gitignore_spec_at_path(directory),
|
|
}
|
|
|
|
file_paths = []
|
|
|
|
for entry in os.scandir(directory):
|
|
if path_is_ignored(Path(entry.path), gitignore_dict):
|
|
continue
|
|
|
|
if entry.is_file():
|
|
if any(entry.path.endswith(ext) for ext in extensions):
|
|
continue
|
|
|
|
file_paths.append(entry.path)
|
|
|
|
elif entry.is_dir():
|
|
subdir_file_paths = get_nonignored_file_paths(
|
|
entry.path, gitignore_dict=gitignore_dict
|
|
)
|
|
file_paths.extend(subdir_file_paths)
|
|
if return_relative:
|
|
file_paths = [os.path.relpath(f, directory) for f in file_paths]
|
|
file_paths.sort(key=lambda p: ("/" in p, p))
|
|
|
|
return file_paths
|
|
|
|
|
|
def path_is_ignored(path: Path, gitignore_dict) -> bool:
|
|
for gitignore_path, pattern in gitignore_dict.items():
|
|
try:
|
|
abspath = path if path.is_absolute() else Path.cwd() / path
|
|
normalized_path = abspath.resolve()
|
|
try:
|
|
relative_path = normalized_path.relative_to(gitignore_path).as_posix()
|
|
except ValueError:
|
|
return False
|
|
|
|
except OSError:
|
|
return False
|
|
|
|
if pattern.match_file(relative_path):
|
|
return True
|
|
return False
|