From 2593af2c5d211b58e28f7c1472f1f67e6783216a Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 9 Jul 2023 15:24:01 -0700 Subject: [PATCH] Improve performance by skipping unnecessary normalisation (#3751) This speeds up black by about 40% when the cache is full --- CHANGES.md | 1 + src/black/files.py | 23 +++++++++++++++++------ tests/test_black.py | 4 +++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 15027afbf0b..93d8ee1921a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -55,6 +55,7 @@ +- Speed up _Black_ significantly when the cache is full (#3751) - Avoid importing `IPython` in a case where we wouldn't need it (#3748) ### Output diff --git a/src/black/files.py b/src/black/files.py index 4e2209e557d..ef6895ee3af 100644 --- a/src/black/files.py +++ b/src/black/files.py @@ -276,15 +276,24 @@ def normalize_path_maybe_ignore( return root_relative_path -def path_is_ignored( - path: Path, gitignore_dict: Dict[Path, PathSpec], report: Report +def _path_is_ignored( + root_relative_path: str, + root: Path, + gitignore_dict: Dict[Path, PathSpec], + report: Report, ) -> bool: + path = root / root_relative_path + # Note that this logic is sensitive to the ordering of gitignore_dict. Callers must + # ensure that gitignore_dict is ordered from least specific to most specific. for gitignore_path, pattern in gitignore_dict.items(): - relative_path = normalize_path_maybe_ignore(path, gitignore_path, report) - if relative_path is None: + try: + relative_path = path.relative_to(gitignore_path).as_posix() + except ValueError: break if pattern.match_file(relative_path): - report.path_ignored(path, "matches a .gitignore file content") + report.path_ignored( + path.relative_to(root), "matches a .gitignore file content" + ) return True return False @@ -326,7 +335,9 @@ def gen_python_files( continue # First ignore files matching .gitignore, if passed - if gitignore_dict and path_is_ignored(child, gitignore_dict, report): + if gitignore_dict and _path_is_ignored( + normalized_path, root, gitignore_dict, report + ): continue # Then ignore with `--exclude` `--extend-exclude` and `--force-exclude` options. diff --git a/tests/test_black.py b/tests/test_black.py index dd21d0a7ae6..3b3ab721c5f 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -508,6 +508,8 @@ def _mocked_calls() -> bool: "pathlib.Path.cwd", return_value=working_directory ), patch("pathlib.Path.is_dir", side_effect=mock_n_calls([True])): ctx = FakeContext() + # Note that the root folder (project_root) isn't the folder + # named "root" (aka working_directory) ctx.obj["root"] = project_root report = MagicMock(verbose=True) black.get_sources( @@ -527,7 +529,7 @@ def _mocked_calls() -> bool: for _, mock_args, _ in report.path_ignored.mock_calls ), "A symbolic link was reported." report.path_ignored.assert_called_once_with( - Path("child", "b.py"), "matches a .gitignore file content" + Path("root", "child", "b.py"), "matches a .gitignore file content" ) def test_report_verbose(self) -> None: