Skip to content

Commit 8d5eb36

Browse files
authored
Fix patterns from gitignore affecting absolute path to project (#645)
Fix patterns from gitignore affecting absolute path to project
1 parent 6f2582c commit 8d5eb36

File tree

5 files changed

+43
-17
lines changed

5 files changed

+43
-17
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Gitignore patterns incorrectly matching paths (#632)
2+
----------------------------------------------------
3+
4+
Robotidy loads the ``.gitignore`` files to not format ignored files and directories. There was a bug with how the paths
5+
are resolved which led to Robotidy ignoring too many paths. For example given project at ``/tmp/a/b/c/d/my-project``
6+
path, if ``.gitignore`` file contained ``tmp/`` pattern it matched whole project (``/tmp/a/b/c/d/my-project``)
7+
instead of path relative to project root (``/tmp/a/b/c/d/my-project/tmp/``).
8+
9+
Now Robotidy resolve paths correctly and such paths should be handled correctly.

robotidy/files.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
from pathlib import Path
33
from typing import Any, Dict, Iterable, Iterator, List, Optional, Pattern, Tuple
44

5+
import pathspec
6+
57
try:
68
import rich_click as click
79
except ImportError:
810
import click
911

1012
import tomli
11-
from pathspec import PathSpec
1213

1314
DEFAULT_EXCLUDES = r"/(\.direnv|\.eggs|\.git|\.hg|\.nox|\.tox|\.venv|venv|\.svn)/"
1415
INCLUDE_EXT = (".robot", ".resource")
@@ -39,7 +40,7 @@ def find_source_config_file(src: Path, ignore_git_dir: bool = False) -> Optional
3940

4041

4142
@lru_cache()
42-
def find_project_root(srcs: Iterable[str], ignore_git_dir: bool = False) -> Path:
43+
def find_project_root(srcs: Tuple[str], ignore_git_dir: bool = False) -> Path:
4344
"""Return a directory containing .git, or robotidy.toml.
4445
That directory will be a common parent of all files and directories
4546
passed in `srcs`.
@@ -85,33 +86,46 @@ def read_pyproject_config(config_path: Path) -> Dict[str, Any]:
8586

8687

8788
@lru_cache()
88-
def get_gitignore(root: Path) -> PathSpec:
89+
def get_gitignore(root: Path) -> pathspec.PathSpec:
8990
"""Return a PathSpec matching gitignore content if present."""
9091
gitignore = root / ".gitignore"
9192
lines: List[str] = []
9293
if gitignore.is_file():
9394
with gitignore.open(encoding="utf-8") as gf:
9495
lines = gf.readlines()
95-
return PathSpec.from_lines("gitwildmatch", lines)
96+
return pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, lines)
9697

9798

9899
def should_parse_path(
99-
path: Path, exclude: Optional[Pattern[str]], extend_exclude: Optional[Pattern[str]], gitignore: Optional[PathSpec]
100+
path: Path,
101+
root_parent: Path,
102+
exclude: Optional[Pattern[str]],
103+
extend_exclude: Optional[Pattern[str]],
104+
gitignore: Optional[pathspec.PathSpec],
100105
) -> bool:
101106
normalized_path = str(path)
102107
for pattern in (exclude, extend_exclude):
103108
match = pattern.search(normalized_path) if pattern else None
104109
if bool(match and match.group(0)):
105110
return False
106-
if gitignore is not None and gitignore.match_file(path):
107-
return False
111+
if gitignore is not None:
112+
relative_path = get_path_relative_to_project_root(path, root_parent)
113+
if gitignore.match_file(relative_path):
114+
return False
108115
if path.is_file():
109116
return path.suffix in INCLUDE_EXT
110117
if exclude and exclude.match(path.name):
111118
return False
112119
return True
113120

114121

122+
def get_path_relative_to_project_root(path: Path, root_parent: Path) -> Path:
123+
try:
124+
return path.relative_to(root_parent)
125+
except ValueError:
126+
return path
127+
128+
115129
def get_paths(
116130
src: Tuple[str, ...], exclude: Optional[Pattern], extend_exclude: Optional[Pattern], skip_gitignore: bool
117131
):
@@ -126,12 +140,13 @@ def get_paths(
126140
sources.add("-")
127141
continue
128142
path = Path(s).resolve()
129-
if not should_parse_path(path, exclude, extend_exclude, gitignore):
143+
root_parent = root.parent if root.parent else root
144+
if not should_parse_path(path, root_parent, exclude, extend_exclude, gitignore):
130145
continue
131146
if path.is_file():
132147
sources.add(path)
133148
elif path.is_dir():
134-
sources.update(iterate_dir((path,), exclude, extend_exclude, gitignore))
149+
sources.update(iterate_dir((path,), exclude, extend_exclude, root_parent, gitignore))
135150
elif s == "-":
136151
sources.add(path)
137152

@@ -142,16 +157,18 @@ def iterate_dir(
142157
paths: Iterable[Path],
143158
exclude: Optional[Pattern],
144159
extend_exclude: Optional[Pattern],
145-
gitignore: Optional[PathSpec],
160+
root_parent: Path,
161+
gitignore: Optional[pathspec.PathSpec],
146162
) -> Iterator[Path]:
147163
for path in paths:
148-
if not should_parse_path(path, exclude, extend_exclude, gitignore):
164+
if not should_parse_path(path, root_parent, exclude, extend_exclude, gitignore):
149165
continue
150166
if path.is_dir():
151167
yield from iterate_dir(
152168
path.iterdir(),
153169
exclude,
154170
extend_exclude,
171+
root_parent,
155172
gitignore + get_gitignore(path) if gitignore is not None else None,
156173
)
157174
elif path.is_file():

robotidy/transformers/NormalizeTags.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ def format_with_case_function(self, string: str) -> str:
8383
tag += self.CASE_FUNCTIONS[self.case_function](match.after)
8484
if var_found:
8585
return tag
86-
else:
87-
return self.CASE_FUNCTIONS[self.case_function](string)
86+
return self.CASE_FUNCTIONS[self.case_function](string)
8887

8988
def normalize_tags_tokens_preserve_formatting(self, node):
9089
if not self.normalize_case:

tests/utest/test_cli.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def test_invalid_argument_type_for_transform(self):
152152

153153
def test_find_project_root_from_src(self):
154154
src = TEST_DATA_DIR / "nested" / "test.robot"
155-
path = find_project_root((src,))
155+
path = find_project_root((str(src),))
156156
assert path == TEST_DATA_DIR / "nested"
157157

158158
def test_ignore_git_dir(self):
@@ -161,9 +161,9 @@ def test_ignore_git_dir(self):
161161
(src / ".git").mkdir(exist_ok=True)
162162
root_with_git = src
163163
root_without_git = TEST_DATA_DIR / "with_git_dir"
164-
path = find_project_root((src,), ignore_git_dir=False)
164+
path = find_project_root((str(src),), ignore_git_dir=False)
165165
assert path == root_with_git
166-
path = find_project_root((src,), ignore_git_dir=True)
166+
path = find_project_root((str(src),), ignore_git_dir=True)
167167
assert path == root_without_git
168168

169169
def test_read_robotidy_config(self):
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
test2.robot
1+
test2.robot
2+
repo/

0 commit comments

Comments
 (0)