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
8 changes: 5 additions & 3 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ jobs:
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
python-version: "3.14"
- name: Use local whoowns package
run: echo "whoowns @ file://${PWD}/packages/whoowns" > constraints-e2e.txt
- name: Prepare local package constraints
run: |
echo "whoowns @ file://${PWD}/packages/whoowns" > constraints-e2e.txt
echo "pre-commit-excludes @ file://${PWD}/packages/pre_commit_excludes" >> constraints-e2e.txt
- name: Run ${{ matrix.tool }} try repo
env:
# Do not set UV_CONSTRAINT here: prek/uv already resolves whoowns from the copied try-repo workspace.
# Do not set UV_CONSTRAINT here: prek/uv already resolves local packages from the copied try-repo workspace.
# Constraining uv to the original checkout creates conflicting file URLs for the same package.
PIP_CONSTRAINT: ${{ github.workspace }}/constraints-e2e.txt
run: SKIP=check-jira-reference-in-todo,go-revive,go-fmt,go-imports,check-number-of-lines-count uvx "${{ matrix.tool }}" try-repo "$PWD" --all-files --color=always --verbose
33 changes: 33 additions & 0 deletions .github/workflows/publish-pre-commit-excludes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Publish pre-commit-excludes to PyPI

on:
push:
tags:
- 'pre-commit-excludes-*'

permissions:
contents: read
packages: read

jobs:
build-and-publish:
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/pre-commit-excludes
permissions:
id-token: write # Required for trusted (OIDC) publishing
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Validate tag version against pyproject.toml
run: ./scripts/check_release.sh "${{ github.ref_name }}"
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
python-version: "3.12"
- name: Build pre-commit-excludes package
run: uv build --package pre-commit-excludes
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
with:
packages-dir: dist/
94 changes: 1 addition & 93 deletions dev_tools/check_useless_exclude_paths_hooks.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,15 @@
# Copyright (c) Luminar Technologies, Inc. All rights reserved.
# Licensed under the MIT License.

from __future__ import annotations

import itertools
import sys
from collections import Counter
from pathlib import Path
from typing import Any

from ruamel.yaml import YAML
from pre_commit_excludes.hook_utils import load_hooks

CONFIG_FILE = ".pre-commit-config.yaml"


class Hook:
"""Represent a pre-commit hook with its excluded paths."""

def __init__(self, id: str, exclude_paths: list[Path]) -> None:
self.__id = id
self.__exclude_paths = exclude_paths

@classmethod
def from_hook_config(cls: type[Hook], root_directory: Path, hook_config: dict[str, str]) -> Hook:
excluded_paths_list = [
root_directory / Path(exclude) for exclude in extract_literal_exclude_paths(hook_config["exclude"])
]

return cls(hook_config["id"], excluded_paths_list)

@property
def id(self) -> str:
return self.__id

@property
def exclude_paths(self) -> list[Path]:
return self.__exclude_paths

def find_duplicates(self) -> list[Path]:
counter = Counter(self.exclude_paths)

return [path for path in counter if counter[path] > 1]

def find_non_existing_paths(self) -> list[Path]:
return [path for path in self.exclude_paths if not path.resolve().exists()]

def has_duplicates(self) -> bool:
return bool(self.find_duplicates())

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 ["*", "$", "^"])


def extract_literal_exclude_paths(exclude_regex: str) -> list[str]:
exclude_list = (
_remove_verbose_regex_comments(exclude_regex)
.replace("\n", "")
.replace(" ", "")
.replace("(?x)^(", "")
.replace("^", "")
.replace(r"\.", ".")
.replace(")", "")
.split("|")
)

return [exclude for exclude in exclude_list if not is_regex_pattern(exclude)]


def _remove_verbose_regex_comments(exclude: str) -> str:
return "\n".join(line.split("#", 1)[0] for line in exclude.splitlines())


def has_excludes(hook_config: dict[str, str]) -> bool:
return bool(hook_config.get("exclude")) and hook_config.get("exclude") != "^$"


def load_config(config_file: Path) -> dict[str, Any]:
yaml = YAML(typ="safe")
config = yaml.load(config_file.read_text(encoding="utf-8"))

return config if isinstance(config, dict) else {}


def load_hooks(root_directory: Path, config_file: Path) -> list[Hook]:
config = load_config(config_file)
hook_configs = itertools.chain(*[repo["hooks"] for repo in config["repos"]])

return [Hook.from_hook_config(root_directory, hook) for hook in hook_configs if has_excludes(hook)]


def have_non_existent_paths_or_duplicates(hooks_list: list[Any]) -> bool:
non_existing_paths: list[tuple[str, str]] = [
(hook_instance.id, path)
Expand Down
3 changes: 1 addition & 2 deletions dev_tools/print_pre_commit_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
from pathlib import Path

from pre_commit.constants import CONFIG_FILE

from dev_tools.check_useless_exclude_paths_hooks import Hook, load_hooks
from pre_commit_excludes.hook_utils import Hook, load_hooks


def parse_arguments() -> Namespace:
Expand Down
1 change: 1 addition & 0 deletions packages/pre_commit_excludes/LICENSE
7 changes: 7 additions & 0 deletions packages/pre_commit_excludes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Pre-Commit-Excludes

<!-- rumdl-disable MD013 -->
[![PyPI](https://img.shields.io/pypi/v/pre-commit-excludes)](https://pypi.org/project/pre-commit-excludes/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pre-commit-excludes)](https://pypi.org/project/pre-commit-excludes/)
[![PyPI - License](https://img.shields.io/pypi/l/pre-commit-excludes)](https://pypi.org/project/pre-commit-excludes/)
<!-- rumdl-enable MD013 -->
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tools to maintain pre-commit exclude lists."""
96 changes: 96 additions & 0 deletions packages/pre_commit_excludes/pre_commit_excludes/hook_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from __future__ import annotations

import itertools
from collections import Counter
from pathlib import Path
from typing import Any

from ruamel.yaml import YAML


class Hook:
"""Represent a pre-commit hook with its excluded paths."""

def __init__(self, id: str, exclude_paths: list[Path]) -> None:
self.__id = id
self.__exclude_paths = exclude_paths

@classmethod
def from_hook_config(cls: type[Hook], root_directory: Path, hook_config: dict[str, str]) -> Hook:
excluded_paths_list = [
root_directory / Path(exclude) for exclude in extract_literal_exclude_paths(hook_config["exclude"])
]

return cls(hook_config["id"], excluded_paths_list)

@property
def id(self) -> str:
return self.__id

@property
def exclude_paths(self) -> list[Path]:
return self.__exclude_paths

def find_duplicates(self) -> list[Path]:
counter = Counter(self.exclude_paths)

return [path for path in counter if counter[path] > 1]

def find_non_existing_paths(self) -> list[Path]:
return [path for path in self.exclude_paths if not path.resolve().exists()]

def has_duplicates(self) -> bool:
return bool(self.find_duplicates())

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 ["*", "$", "^"])


def extract_literal_exclude_paths(exclude_regex: str) -> list[str]:
exclude_list = (
_remove_verbose_regex_comments(exclude_regex)
.replace("\n", "")
.replace(" ", "")
.replace("(?x)^(", "")
.replace("^", "")
.replace(r"\.", ".")
.replace(")", "")
.split("|")
)

return [exclude for exclude in exclude_list if not is_regex_pattern(exclude)]


def _remove_verbose_regex_comments(exclude: str) -> str:
return "\n".join(line.split("#", 1)[0] for line in exclude.splitlines())


def has_excludes(hook_config: dict[str, str]) -> bool:
return bool(hook_config.get("exclude")) and hook_config.get("exclude") != "^$"


def load_config(config_file: Path) -> dict[str, Any]:
yaml = YAML(typ="safe")
config = yaml.load(config_file.read_text(encoding="utf-8"))

return config if isinstance(config, dict) else {}


def load_hooks(root_directory: Path, config_file: Path) -> list[Hook]:
config = load_config(config_file)
hook_configs = itertools.chain(*[repo["hooks"] for repo in config["repos"]])

return [Hook.from_hook_config(root_directory, hook) for hook in hook_configs if has_excludes(hook)]
38 changes: 38 additions & 0 deletions packages/pre_commit_excludes/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[build-system]
requires = ["uv_build>=0.10.0,<0.12"]
build-backend = "uv_build"

[project]
name = "pre_commit_excludes"
version = "0.1.0"
description = "Tools to maintain pre-commit exclude lists"
readme = "README.md"
authors = [
{name = "Markus Hofbauer"},
{name = "Chris Bachhuber"}
]
license = "MIT"
license-files = ["LICENSE"]
requires-python = ">=3.10"
dependencies = []
keywords = ["pre-commit", "prek"]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Software Development :: Version Control :: Git"
]

[project.urls]
Homepage = "https://github.com/hofbi/dev-tools"
Repository = "https://github.com/hofbi/dev-tools"
Issues = "https://github.com/hofbi/dev-tools/issues"

[tool.uv.build-backend]
module-root = "" # required to avoid the "src" layout, which is the default for uv_build
10 changes: 8 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies = [
"pyjson5>=1.6.8",
"regex>=2026.2.28",
"ruamel-yaml>=0.19.1",
"pre-commit-excludes",
"configure-vscode-for-bazel",
"whoowns"
]
Expand Down Expand Up @@ -46,7 +47,7 @@ sync-vscode-config = "dev_tools.sync_vscode_config:main"
sync-tool-versions = "dev_tools.sync_tool_versions:main"

[tool.pytest.ini_options]
addopts = "--cov-report term-missing --cov=dev_tools --cov=packages/whoowns/whoowns --cov=packages/configure_vscode_for_bazel/configure_vscode_for_bazel -vv"
addopts = "--cov-report term-missing --cov=dev_tools --cov=packages/whoowns/whoowns --cov=packages/pre_commit_excludes/pre_commit_excludes --cov=packages/configure_vscode_for_bazel/configure_vscode_for_bazel -vv"
testpaths = [
"tests"
]
Expand Down Expand Up @@ -92,10 +93,15 @@ module-root = "" # required to avoid the "src" layout, which is the default for

[tool.uv.sources]
configure-vscode-for-bazel = {workspace = true}
pre-commit-excludes = {workspace = true}
whoowns = {workspace = true}

[tool.uv.workspace]
members = ["packages/configure_vscode_for_bazel", "packages/whoowns"]
members = [
"packages/configure_vscode_for_bazel",
"packages/pre_commit_excludes",
"packages/whoowns"
]

[tool.vulture]
min_confidence = 100
Expand Down
1 change: 1 addition & 0 deletions tests/pre_commit_excludes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for packages/pre_commit_excludes."""
Loading