From 1fe2efd8573a63ffc76c69320720d349b21897da Mon Sep 17 00:00:00 2001 From: Kaleb Barrett Date: Fri, 7 May 2021 07:54:21 -0500 Subject: [PATCH] Do not use gitignore if explicitly passing excludes (#2170) Closes #2164. Changes behavior of how .gitignore is handled. With this change, the rules in .gitignore are only used as a fallback if no exclusion rule is explicitly passed on the command line or in pyproject.toml. Previously they were used regardless if explicit exclusion rules were specified, preventing any overriding of .gitignore rules. Those that depend only on .gitignore for their exclusion rules will not be affected. Those that use both .gitignore and exclude will find that exclude will act more like actually specifying exclude and not just another extra-excludes. If the previous behavior was desired, they should move their rules from exclude to extra-excludes. --- CHANGES.md | 3 ++ src/black/__init__.py | 25 ++++++++++------- tests/data/include_exclude_tests/.gitignore | 1 + .../data/include_exclude_tests/pyproject.toml | 3 ++ tests/test_black.py | 28 +++++++++++++++++++ 5 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 tests/data/include_exclude_tests/.gitignore create mode 100644 tests/data/include_exclude_tests/pyproject.toml diff --git a/CHANGES.md b/CHANGES.md index becf699..b81e6eb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,9 @@ [circumstances](https://github.com/psf/black/blob/master/docs/the_black_code_style.md#pragmatism) in which _Black_ may change the AST (#2159) +- Allow `.gitignore` rules to be overridden by specifying `exclude` in `pyproject.toml` + or on the command line. (#2170) + #### _Packaging_ - Install `primer.json` (used by `black-primer` by default) with black. (#2154) diff --git a/src/black/__init__.py b/src/black/__init__.py index cf25787..e47aa21 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -492,15 +492,15 @@ def validate_regex( @click.option( "--exclude", type=str, - default=DEFAULT_EXCLUDES, callback=validate_regex, help=( "A regular expression that matches files and directories that should be" " excluded on recursive searches. An empty value means no paths are excluded." " Use forward slashes for directories on all platforms (Windows, too)." - " Exclusions are calculated first, inclusions later." + " Exclusions are calculated first, inclusions later. [default:" + f" {DEFAULT_EXCLUDES}]" ), - show_default=True, + show_default=False, ) @click.option( "--extend-exclude", @@ -587,7 +587,7 @@ def main( quiet: bool, verbose: bool, include: Pattern, - exclude: Pattern, + exclude: Optional[Pattern], extend_exclude: Optional[Pattern], force_exclude: Optional[Pattern], stdin_filename: Optional[str], @@ -662,7 +662,7 @@ def get_sources( quiet: bool, verbose: bool, include: Pattern[str], - exclude: Pattern[str], + exclude: Optional[Pattern[str]], extend_exclude: Optional[Pattern[str]], force_exclude: Optional[Pattern[str]], report: "Report", @@ -673,7 +673,12 @@ def get_sources( root = find_project_root(src) sources: Set[Path] = set() path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx) - gitignore = get_gitignore(root) + + if exclude is None: + exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES) + gitignore = get_gitignore(root) + else: + gitignore = None for s in src: if s == "-" and stdin_filename: @@ -6215,12 +6220,12 @@ def path_is_excluded( def gen_python_files( paths: Iterable[Path], root: Path, - include: Optional[Pattern[str]], + include: Pattern[str], exclude: Pattern[str], extend_exclude: Optional[Pattern[str]], force_exclude: Optional[Pattern[str]], report: "Report", - gitignore: PathSpec, + gitignore: Optional[PathSpec], ) -> Iterator[Path]: """Generate all files under `path` whose paths are not excluded by the `exclude_regex`, `extend_exclude`, or `force_exclude` regexes, @@ -6236,8 +6241,8 @@ def gen_python_files( if normalized_path is None: continue - # First ignore files matching .gitignore - if gitignore.match_file(normalized_path): + # First ignore files matching .gitignore, if passed + if gitignore is not None and gitignore.match_file(normalized_path): report.path_ignored(child, "matches the .gitignore file content") continue diff --git a/tests/data/include_exclude_tests/.gitignore b/tests/data/include_exclude_tests/.gitignore new file mode 100644 index 0000000..91f3456 --- /dev/null +++ b/tests/data/include_exclude_tests/.gitignore @@ -0,0 +1 @@ +dont_exclude/ diff --git a/tests/data/include_exclude_tests/pyproject.toml b/tests/data/include_exclude_tests/pyproject.toml new file mode 100644 index 0000000..9ba7ec2 --- /dev/null +++ b/tests/data/include_exclude_tests/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=41.0", "setuptools-scm", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/tests/test_black.py b/tests/test_black.py index 7d855ca..9b2bfcd 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -1418,6 +1418,32 @@ class BlackTestCase(BlackBaseTestCase): ) self.assertEqual(sorted(expected), sorted(sources)) + def test_gitingore_used_as_default(self) -> None: + path = Path(THIS_DIR / "data" / "include_exclude_tests") + include = re.compile(r"\.pyi?$") + extend_exclude = re.compile(r"/exclude/") + src = str(path / "b/") + report = black.Report() + expected: List[Path] = [ + path / "b/.definitely_exclude/a.py", + path / "b/.definitely_exclude/a.pyi", + ] + sources = list( + black.get_sources( + ctx=FakeContext(), + src=(src,), + quiet=True, + verbose=False, + include=include, + exclude=None, + extend_exclude=extend_exclude, + force_exclude=None, + report=report, + stdin_filename=None, + ) + ) + self.assertEqual(sorted(expected), sorted(sources)) + @patch("black.find_project_root", lambda *args: THIS_DIR.resolve()) def test_exclude_for_issue_1572(self) -> None: # Exclude shouldn't touch files that were explicitly given to Black through the @@ -1705,6 +1731,8 @@ class BlackTestCase(BlackBaseTestCase): Path(path / "b/.definitely_exclude/a.pie"), Path(path / "b/.definitely_exclude/a.py"), Path(path / "b/.definitely_exclude/a.pyi"), + Path(path / ".gitignore"), + Path(path / "pyproject.toml"), ] this_abs = THIS_DIR.resolve() sources.extend( -- 2.39.2