Skip to content

Commit 8fe602b

Browse files
fix pathlib exception handling with symlinks (#4161)
Fixes #4077
1 parent 6f3fb78 commit 8fe602b

File tree

4 files changed

+37
-9
lines changed

4 files changed

+37
-9
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
<!-- Changes to how Black can be configured -->
3131

32+
- Fix symlink handling, properly catch and ignore symlinks that point outside of root
33+
(#4161)
3234
- Fix cache mtime logic that resulted in false positive cache hits (#4128)
3335

3436
### Packaging

src/black/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
find_user_pyproject_toml,
5050
gen_python_files,
5151
get_gitignore,
52+
get_root_relative_path,
5253
normalize_path_maybe_ignore,
5354
parse_pyproject_toml,
5455
path_is_excluded,
@@ -700,7 +701,10 @@ def get_sources(
700701

701702
# Compare the logic here to the logic in `gen_python_files`.
702703
if is_stdin or path.is_file():
703-
root_relative_path = path.absolute().relative_to(root).as_posix()
704+
root_relative_path = get_root_relative_path(path, root, report)
705+
706+
if root_relative_path is None:
707+
continue
704708

705709
root_relative_path = "/" + root_relative_path
706710

src/black/files.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -259,14 +259,7 @@ def normalize_path_maybe_ignore(
259259
try:
260260
abspath = path if path.is_absolute() else Path.cwd() / path
261261
normalized_path = abspath.resolve()
262-
try:
263-
root_relative_path = normalized_path.relative_to(root).as_posix()
264-
except ValueError:
265-
if report:
266-
report.path_ignored(
267-
path, f"is a symbolic link that points outside {root}"
268-
)
269-
return None
262+
root_relative_path = get_root_relative_path(normalized_path, root, report)
270263

271264
except OSError as e:
272265
if report:
@@ -276,6 +269,21 @@ def normalize_path_maybe_ignore(
276269
return root_relative_path
277270

278271

272+
def get_root_relative_path(
273+
path: Path,
274+
root: Path,
275+
report: Optional[Report] = None,
276+
) -> Optional[str]:
277+
"""Returns the file path relative to the 'root' directory"""
278+
try:
279+
root_relative_path = path.absolute().relative_to(root).as_posix()
280+
except ValueError:
281+
if report:
282+
report.path_ignored(path, f"is a symbolic link that points outside {root}")
283+
return None
284+
return root_relative_path
285+
286+
279287
def _path_is_ignored(
280288
root_relative_path: str,
281289
root: Path,

tests/test_black.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2592,6 +2592,20 @@ def test_symlinks(self) -> None:
25922592
outside_root_symlink.resolve.assert_called_once()
25932593
ignored_symlink.resolve.assert_not_called()
25942594

2595+
def test_get_sources_with_stdin_symlink_outside_root(
2596+
self,
2597+
) -> None:
2598+
path = THIS_DIR / "data" / "include_exclude_tests"
2599+
stdin_filename = str(path / "b/exclude/a.py")
2600+
outside_root_symlink = Path("/target_directory/a.py")
2601+
with patch("pathlib.Path.resolve", return_value=outside_root_symlink):
2602+
assert_collected_sources(
2603+
root=Path("target_directory/"),
2604+
src=["-"],
2605+
expected=[],
2606+
stdin_filename=stdin_filename,
2607+
)
2608+
25952609
@patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
25962610
def test_get_sources_with_stdin(self) -> None:
25972611
src = ["-"]

0 commit comments

Comments
 (0)