Skip to content

Commit

Permalink
Fix handling of Windows junctions in normalize_path_maybe_ignore (#2904)
Browse files Browse the repository at this point in the history
Fixes #2569
  • Loading branch information
yoerg committed Mar 8, 2022
1 parent fd6e92a commit 24ffc54
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 36 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -53,6 +53,8 @@
- Black can now parse starred expressions in the target of `for` and `async for`
statements, e.g `for item in *items_1, *items_2: pass` (#2879).

- Fix handling of directory junctions on Windows (#2904)

### Performance

<!-- Changes that improve Black's performance. -->
Expand Down
19 changes: 9 additions & 10 deletions src/black/files.py
Expand Up @@ -151,23 +151,22 @@ def normalize_path_maybe_ignore(
"""
try:
abspath = path if path.is_absolute() else Path.cwd() / path
normalized_path = abspath.resolve().relative_to(root).as_posix()
except OSError as e:
if report:
report.path_ignored(path, f"cannot be read because {e}")
return None

except ValueError:
if path.is_symlink():
normalized_path = abspath.resolve()
try:
root_relative_path = normalized_path.relative_to(root).as_posix()
except ValueError:
if report:
report.path_ignored(
path, f"is a symbolic link that points outside {root}"
)
return None

raise
except OSError as e:
if report:
report.path_ignored(path, f"cannot be read because {e}")
return None

return normalized_path
return root_relative_path


def path_is_excluded(
Expand Down
45 changes: 19 additions & 26 deletions tests/test_black.py
Expand Up @@ -1418,6 +1418,25 @@ def test_bpo_33660_workaround(self) -> None:
normalized_path = black.normalize_path_maybe_ignore(path, root, report)
self.assertEqual(normalized_path, "workspace/project")

def test_normalize_path_ignore_windows_junctions_outside_of_root(self) -> None:
if system() != "Windows":
return

with TemporaryDirectory() as workspace:
root = Path(workspace)
junction_dir = root / "junction"
junction_target_outside_of_root = root / ".."
os.system(f"mklink /J {junction_dir} {junction_target_outside_of_root}")

report = black.Report(verbose=True)
normalized_path = black.normalize_path_maybe_ignore(
junction_dir, root, report
)
# Manually delete for Python < 3.8
os.system(f"rmdir {junction_dir}")

self.assertEqual(normalized_path, None)

def test_newline_comment_interaction(self) -> None:
source = "class A:\\\r\n# type: ignore\n pass\n"
output = black.format_str(source, mode=DEFAULT_MODE)
Expand Down Expand Up @@ -1994,7 +2013,6 @@ def test_symlink_out_of_root_directory(self) -> None:
path.iterdir.return_value = [child]
child.resolve.return_value = Path("/a/b/c")
child.as_posix.return_value = "/a/b/c"
child.is_symlink.return_value = True
try:
list(
black.gen_python_files(
Expand All @@ -2014,31 +2032,6 @@ def test_symlink_out_of_root_directory(self) -> None:
pytest.fail(f"`get_python_files_in_dir()` failed: {ve}")
path.iterdir.assert_called_once()
child.resolve.assert_called_once()
child.is_symlink.assert_called_once()
# `child` should behave like a strange file which resolved path is clearly
# outside of the `root` directory.
child.is_symlink.return_value = False
with pytest.raises(ValueError):
list(
black.gen_python_files(
path.iterdir(),
root,
include,
exclude,
None,
None,
report,
gitignore,
verbose=False,
quiet=False,
)
)
path.iterdir.assert_called()
assert path.iterdir.call_count == 2
child.resolve.assert_called()
assert child.resolve.call_count == 2
child.is_symlink.assert_called()
assert child.is_symlink.call_count == 2

@patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
def test_get_sources_with_stdin(self) -> None:
Expand Down

0 comments on commit 24ffc54

Please sign in to comment.