Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix handling of Windows junctions in normalize_path_maybe_ignore (#2569) #2904

Merged
merged 3 commits into from Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -49,6 +49,8 @@

<!-- Changes to the parser or to version autodetection -->

- 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):
Copy link
Contributor Author

@yoerg yoerg Mar 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is a slight behavioral change: black will no longer raise an exception for these strange files (e.g. Windows directory junctions), ignoring them just like symlinks. I don't know of any other strange links, but I can't imagine a benefit in stopping the analysis for any of them.

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