From 24ffc54a53b52293a54d7ef9f2105c26e945cc67 Mon Sep 17 00:00:00 2001 From: yoerg <73831825+yoerg@users.noreply.github.com> Date: Tue, 8 Mar 2022 16:28:13 +0100 Subject: [PATCH] Fix handling of Windows junctions in normalize_path_maybe_ignore (#2904) Fixes #2569 --- CHANGES.md | 2 ++ src/black/files.py | 19 +++++++++---------- tests/test_black.py | 45 +++++++++++++++++++-------------------------- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ac9212e..4264e63 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/src/black/files.py b/src/black/files.py index 8348e0d..b6c1cf3 100644 --- a/src/black/files.py +++ b/src/black/files.py @@ -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( diff --git a/tests/test_black.py b/tests/test_black.py index 82abd47..b1bf177 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -1418,6 +1418,25 @@ class BlackTestCase(BlackBaseTestCase): 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) @@ -1994,7 +2013,6 @@ class TestFileCollection: 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( @@ -2014,31 +2032,6 @@ class TestFileCollection: 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: -- 2.39.5