Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ on:
jobs:
check:
runs-on: ubuntu-latest

name: test with ${{ matrix.py }}
strategy:
fail-fast: false
Expand All @@ -31,3 +30,17 @@ jobs:
run: python -m pip install -r .devcontainer/requirements.txt
- name: Run test suite
run: tox -e py

show-excluded-files-count:
runs-on: ubuntu-latest
name: show excluded files count
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.13"
- name: Install pre-commit
run: python -m pip install pre-commit
- name: Run pre-commit to show excluded files
run: pre-commit run check-non-existing-and-duplicate-excludes --verbose
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ repos:
- id: check-useless-excludes
- repo: local
hooks:
- id: check-non-existing-and-duplicate-excludes
name: Check non-existing and duplicate excludes in pre-commit-config
entry: dev_tools/check_useless_exclude_paths_hooks.py
pass_filenames: false
always_run: true
language: python
additional_dependencies: ["pre-commit >= 3.5.0"]
- id: generate-hook-docs
name: generate hook docs
description: Generate markdown documentation for pre-commit hooks in README.md
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
- svg
- id: check-non-existing-and-duplicate-excludes
name: Check non-existing and duplicate excludes in pre-commit-config
description: "Check for non existing and duplicate paths in `.pre-commit-config.yaml`. Background: In a big codebase, the exclude lists can be quite long and it's easy to make a typo or forget to remove an entry when it's no longer needed."
description: "Check for non existing and duplicate paths in `.pre-commit-config.yaml`. Background: In a big codebase, the exclude lists can be quite long and it's easy to make a typo or forget to remove an entry when it's no longer needed. If you run this hook with `--verbose` it will also print the number of excluded files for each hook."
entry: check-useless-exclude-paths-hooks
pass_filenames: false
always_run: true
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Check that all TODO comments follow the same pattern and link a Jira ticket: `TO

### `check-non-existing-and-duplicate-excludes`

Check for non existing and duplicate paths in `.pre-commit-config.yaml`. Background: In a big codebase, the exclude lists can be quite long and it's easy to make a typo or forget to remove an entry when it's no longer needed.
Check for non existing and duplicate paths in `.pre-commit-config.yaml`. Background: In a big codebase, the exclude lists can be quite long and it's easy to make a typo or forget to remove an entry when it's no longer needed. If you run this hook with `--verbose` it will also print the number of excluded files for each hook.

### `sync-vscode-config`

Expand Down
25 changes: 24 additions & 1 deletion dev_tools/check_useless_exclude_paths_hooks.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env python

# Copyright (c) Luminar Technologies, Inc. All rights reserved.
# Licensed under the MIT License.

Expand Down Expand Up @@ -58,6 +60,15 @@ def has_duplicates(self) -> bool:
def has_non_existing_paths(self) -> bool:
return bool(self.find_non_existing_paths())

def count_excluded_files(self) -> int:
existing_paths = [path for path in self.exclude_paths if path.exists()]
total_file_count = sum(1 for path in existing_paths if path.is_file())
excluded_dirs = [path for path in existing_paths if path.is_dir()]
total_dir_count = sum(
sum(1 for file in excluded_dir.rglob("*") if file.is_file()) for excluded_dir in excluded_dirs
)
return total_file_count + total_dir_count


def is_regex_pattern(exclude: str) -> bool:
return any(regex_key in exclude for regex_key in ["*", "$", "^"])
Expand Down Expand Up @@ -101,10 +112,22 @@ def have_non_existent_paths_or_duplicates(hooks_list: list[Any]) -> bool:
return bool(non_existing_paths or duplicates)


def print_excluded_files_count(hooks_list: list[Any]) -> None:
total_excluded = sum(hook.count_excluded_files() for hook in hooks_list)
print(f"Total number of excluded files: {total_excluded}")

for hook in hooks_list:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not necessarily sorted alphabetically by hook name, right? Shall we sort to get a stable output?

Copy link
Copy Markdown
Collaborator Author

@hofbi hofbi Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order is based on the pre-commit config. To me this sounds like a valid approach and might be helpful to find the hooks in the config. The output is as stable as your config file.

excluded_count = hook.count_excluded_files()
if excluded_count > 0:
print(f"{excluded_count} files excluded from hook {hook.id}")


def main() -> int:
repo_root = Path.cwd()
pre_commit_config = repo_root / CONFIG_FILE
return 1 if have_non_existent_paths_or_duplicates(load_hooks(repo_root, pre_commit_config)) else 0
hooks_list = load_hooks(repo_root, pre_commit_config)
print_excluded_files_count(hooks_list)
return 1 if have_non_existent_paths_or_duplicates(hooks_list) else 0


if __name__ == "__main__":
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ ignore = [
"T201"
]

[tool.ruff.lint.per-file-ignores]
"tests/**" = [
"PLR2004" # Magic values in tests are ok since they are often the expected values
]

[tool.vulture]
min_confidence = 100
ignore_decorators = ["@pytest.fixture"]
Expand Down
60 changes: 60 additions & 0 deletions tests/test_check_useless_exclude_paths_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,63 @@ def test_have_non_existent_paths_or_duplicates_for_duplicate_paths(
assert "duplicates" in output
assert "existing_path1" in output
assert "existing_path2" in output


def test_count_excluded_files_for_single_file(fs: FakeFilesystem) -> None:
fs.create_file(Path("Repo/test_file.txt"))
hook_instance = Hook("test_id", [Path("Repo/test_file.txt")])

assert hook_instance.count_excluded_files() == 1


def test_count_excluded_files_for_multiple_files(fs: FakeFilesystem) -> None:
fs.create_file(Path("Repo/file1.txt"))
fs.create_file(Path("Repo/file2.txt"))
fs.create_file(Path("Repo/file3.txt"))
hook_instance = Hook("test_id", [Path("Repo/file1.txt"), Path("Repo/file2.txt"), Path("Repo/file3.txt")])

assert hook_instance.count_excluded_files() == 3


def test_count_excluded_files_for_directory(fs: FakeFilesystem) -> None:
fs.create_dir(Path("Repo/test_dir"))
fs.create_file(Path("Repo/test_dir/file1.txt"))
fs.create_file(Path("Repo/test_dir/file2.txt"))
fs.create_file(Path("Repo/test_dir/subdir/file3.txt"))
hook_instance = Hook("test_id", [Path("Repo/test_dir")])

assert hook_instance.count_excluded_files() == 3


def test_count_excluded_files_for_mixed_paths(fs: FakeFilesystem) -> None:
fs.create_file(Path("Repo/single_file.txt"))
fs.create_dir(Path("Repo/test_dir"))
fs.create_file(Path("Repo/test_dir/file1.txt"))
fs.create_file(Path("Repo/test_dir/file2.txt"))
hook_instance = Hook("test_id", [Path("Repo/single_file.txt"), Path("Repo/test_dir")])

assert hook_instance.count_excluded_files() == 3


def test_count_excluded_files_for_non_existing_paths() -> None:
hook_instance = Hook("test_id", [Path("Repo/non_existing_file.txt"), Path("Repo/non_existing_dir")])

assert hook_instance.count_excluded_files() == 0


def test_count_excluded_files_for_mixed_existing_and_non_existing_paths(fs: FakeFilesystem) -> None:
fs.create_file(Path("Repo/existing_file.txt"))
fs.create_dir(Path("Repo/existing_dir"))
fs.create_file(Path("Repo/existing_dir/file.txt"))
hook_instance = Hook(
"test_id",
[Path("Repo/existing_file.txt"), Path("Repo/existing_dir"), Path("Repo/non_existing_file.txt")],
)

assert hook_instance.count_excluded_files() == 2


def test_count_excluded_files_for_empty_exclude_paths() -> None:
hook_instance = Hook("test_id", [])

assert hook_instance.count_excluded_files() == 0