From 10caeb3644c4de97767e056ed80673cfe6ac9ecf Mon Sep 17 00:00:00 2001 From: yoerg <73831825+yoerg@users.noreply.github.com> Date: Thu, 3 Mar 2022 14:10:01 +0100 Subject: [PATCH 1/3] Fix handling of Windows junctions in normalize_path_maybe_ignore (#2569) --- CHANGES.md | 2 ++ src/black/files.py | 19 +++++++++---------- tests/test_black.py | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5f9341d048c..1e8698484ad 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -49,6 +49,8 @@ +- Fix handling of directory junctions on Windows (#2904) + ### Performance diff --git a/src/black/files.py b/src/black/files.py index 8348e0d8c28..b6c1cf3ffe1 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 82abd47dffd..2fcc2f7a01b 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -1418,6 +1418,22 @@ 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 + ) + 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) From 6114192a481173553aaabac5659803fe37351aff Mon Sep 17 00:00:00 2001 From: yoerg <73831825+yoerg@users.noreply.github.com> Date: Thu, 3 Mar 2022 17:38:06 +0100 Subject: [PATCH 2/3] Adjust test_symlink_out_of_root_directory to symlink agnostic behavior --- tests/test_black.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/tests/test_black.py b/tests/test_black.py index 2fcc2f7a01b..1954b964aca 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -2010,7 +2010,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( @@ -2030,31 +2029,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: From 6cb749f0225c4650f30c4d1980a098d2253c6691 Mon Sep 17 00:00:00 2001 From: yoerg <73831825+yoerg@users.noreply.github.com> Date: Fri, 4 Mar 2022 07:35:18 +0100 Subject: [PATCH 3/3] Explicitly delete junction test dir Otherwise the tempdir cleanup will fail in rmdir for python <3.8 --- tests/test_black.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_black.py b/tests/test_black.py index 1954b964aca..b1bf1772550 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -1432,6 +1432,9 @@ def test_normalize_path_ignore_windows_junctions_outside_of_root(self) -> None: 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: