diff --git a/CHANGES.md b/CHANGES.md index 7703223a119..71f62d0e11f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,8 @@ - Add support for single line format skip with other comments on the same line (#3959) +- Fix a bug in the matching of absolute path names in `--include` (#3976) + ### Packaging diff --git a/src/black/files.py b/src/black/files.py index 362898dc0fd..1eed7eda828 100644 --- a/src/black/files.py +++ b/src/black/files.py @@ -389,7 +389,7 @@ def gen_python_files( warn=verbose or not quiet ): continue - include_match = include.search(normalized_path) if include else True + include_match = include.search(root_relative_path) if include else True if include_match: yield child diff --git a/tests/test_black.py b/tests/test_black.py index 537ca80d432..56c20243020 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -2388,6 +2388,27 @@ def test_empty_include(self) -> None: # Setting exclude explicitly to an empty string to block .gitignore usage. assert_collected_sources(src, expected, include="", exclude="") + def test_include_absolute_path(self) -> None: + path = DATA_DIR / "include_exclude_tests" + src = [path] + expected = [ + Path(path / "b/dont_exclude/a.pie"), + ] + assert_collected_sources( + src, expected, root=path, include=r"^/b/dont_exclude/a\.pie$", exclude="" + ) + + def test_exclude_absolute_path(self) -> None: + path = DATA_DIR / "include_exclude_tests" + src = [path] + expected = [ + Path(path / "b/dont_exclude/a.py"), + Path(path / "b/.definitely_exclude/a.py"), + ] + assert_collected_sources( + src, expected, root=path, include=r"\.py$", exclude=r"^/b/exclude/a\.py$" + ) + def test_extend_exclude(self) -> None: path = DATA_DIR / "include_exclude_tests" src = [path] @@ -2401,7 +2422,6 @@ def test_extend_exclude(self) -> None: @pytest.mark.incompatible_with_mypyc def test_symlinks(self) -> None: - path = MagicMock() root = THIS_DIR.resolve() include = re.compile(black.DEFAULT_INCLUDES) exclude = re.compile(black.DEFAULT_EXCLUDES) @@ -2409,19 +2429,44 @@ def test_symlinks(self) -> None: gitignore = PathSpec.from_lines("gitwildmatch", []) regular = MagicMock() - outside_root_symlink = MagicMock() - ignored_symlink = MagicMock() - - path.iterdir.return_value = [regular, outside_root_symlink, ignored_symlink] - regular.absolute.return_value = root / "regular.py" regular.resolve.return_value = root / "regular.py" regular.is_dir.return_value = False + regular.is_file.return_value = True + outside_root_symlink = MagicMock() outside_root_symlink.absolute.return_value = root / "symlink.py" outside_root_symlink.resolve.return_value = Path("/nowhere") + outside_root_symlink.is_dir.return_value = False + outside_root_symlink.is_file.return_value = True + ignored_symlink = MagicMock() ignored_symlink.absolute.return_value = root / ".mypy_cache" / "symlink.py" + ignored_symlink.is_dir.return_value = False + ignored_symlink.is_file.return_value = True + + # A symlink that has an excluded name, but points to an included name + symlink_excluded_name = MagicMock() + symlink_excluded_name.absolute.return_value = root / "excluded_name" + symlink_excluded_name.resolve.return_value = root / "included_name.py" + symlink_excluded_name.is_dir.return_value = False + symlink_excluded_name.is_file.return_value = True + + # A symlink that has an included name, but points to an excluded name + symlink_included_name = MagicMock() + symlink_included_name.absolute.return_value = root / "included_name.py" + symlink_included_name.resolve.return_value = root / "excluded_name" + symlink_included_name.is_dir.return_value = False + symlink_included_name.is_file.return_value = True + + path = MagicMock() + path.iterdir.return_value = [ + regular, + outside_root_symlink, + ignored_symlink, + symlink_excluded_name, + symlink_included_name, + ] files = list( black.gen_python_files( @@ -2437,7 +2482,7 @@ def test_symlinks(self) -> None: quiet=False, ) ) - assert files == [regular] + assert files == [regular, symlink_included_name] path.iterdir.assert_called_once() outside_root_symlink.resolve.assert_called_once()