Skip to content

Commit d17b3e9

Browse files
committed
Tools: Add pre-commit hook to check copyright in ros2 files
Add a local pre-commit hook to verify the copyright notice is in all Python files in `Tools/ros2`. Make exceptions for: ```python exclude_file_paths = { "Tools/ros2/ardupilot_dds_tests/setup.py", "Tools/ros2/ardupilot_dds_tests/test/test_copyright.py", "Tools/ros2/ardupilot_dds_tests/test/test_pep257.py", } ``` % `Tools/gittools/pre_commit_copyright.py` ``` Usage: python pre_commit_copyright.py file_path [...] ``` % `Tools/gittools/pre_commit_copyright.py README.md` ``` Copyright not found in: README.md ``` % `Tools/gittools/pre_commit_copyright.py README.md COPYING.txt` ``` Copyright not found in: COPYING.txt, README.md ``` % `Tools/gittools/pre_commit_copyright.py Tools/ros2/ardupilot_sitl/launch/sitl.launch.py` ``` <Nothing because this file has the copyright notice.> ``` % `pre-commit run check-copyright --verbose --all-files` ``` Check copyright notice in files..........................................Passed - hook id: check-copyright - duration: 0.08s ``` Edit `Tools/gittools/pre_commit_copyright.py` and add a `Z` to the front of each of the `exclude_file_paths`. % `pre-commit run check-copyright --verbose --all-files` ``` Check copyright notice in files..........................................Failed - hook id: check-copyright - duration: 0.07s - exit code: 1 Copyright not found in: Tools/ros2/ardupilot_dds_tests/setup.py Copyright not found in: Tools/ros2/ardupilot_dds_tests/test/test_pep257.py Copyright not found in: Tools/ros2/ardupilot_dds_tests/test/test_copyright.py ``` Edit `Tools/gittools/pre_commit_copyright.py` to remove those `Z`'s.
1 parent 21a73eb commit d17b3e9

File tree

4 files changed

+172
-0
lines changed

4 files changed

+172
-0
lines changed

.github/workflows/pre-commit.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@ jobs:
99
steps:
1010
- uses: actions/checkout@v4
1111
- uses: actions/setup-python@v5
12+
with:
13+
python-version: 3.x
1214
- uses: pre-commit/[email protected]
15+
- run: python3 -m pip install --upgrade pip
16+
- run: python3 -m pip install pytest
17+
- run: PYTHONPATH="." pytest tests

.pre-commit-config.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ repos:
5656
hooks:
5757
- id: flake8
5858

59+
- repo: local
60+
hooks:
61+
- id: check-copyright
62+
name: check copyright notice in files
63+
entry: Tools/gittools/pre_commit_copyright.py
64+
language: python
65+
files: |
66+
(?x)^(
67+
Tools/ros2/.*\.py
68+
)$
69+
args: [
70+
--ignore=Tools/ros2/ardupilot_dds_tests/setup.py,
71+
--ignore=Tools/ros2/ardupilot_dds_tests/test/test_pep257.py,
72+
--ignore=Tools/ros2/ardupilot_dds_tests/test/test_copyright.py,
73+
]
74+
5975
# # Use to sort python imports by name and put system import first.
6076
# - repo: https://github.com/pycqa/isort
6177
# rev: 5.12.0
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env python3
2+
3+
# CAUTION: The first docstring in this file will be the copyright notice that will be
4+
# looked for in all file paths that the pre-commit hook passes in. If all files
5+
# contain the __doc__ text below, the commit will be allowed to proceed.
6+
7+
"""ArduPilot.org.
8+
#
9+
# This program is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License as published by
11+
# the Free Software Foundation, either version 3 of the License, or
12+
# (at your option) any later version.
13+
#
14+
# This program is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
21+
"""
22+
23+
# --- Do not merges these two docstrings. See CAUTION above. ---
24+
25+
from argparse import ArgumentParser
26+
from pathlib import Path
27+
28+
r"""
29+
A pre-commit hook that will use the content of __doc__ and then ensure all files
30+
processed contain that same copyright information. This is useful to ensure that all
31+
those files consistent copyright notices, which is important for legal and compliance
32+
reasons. If any files does not contain the expected copyright information, the commit
33+
will be aborted and an error message will be displayed with the file paths of all files
34+
that do not match.
35+
36+
Place an entry into the local `.pre-commit-config.yaml` file to run this job.
37+
```yaml
38+
- repo: local
39+
hooks:
40+
- id: check-copyright
41+
name: check copyright notice in files
42+
entry: Tools/gittools/pre_commit_copyright.py
43+
language: python
44+
files: |
45+
(?x)^(
46+
Tools/ros2/.*\.py
47+
)$
48+
args: [
49+
--ignore=excluded_file.py,
50+
--ignore=another_excluded.py,
51+
]
52+
```
53+
54+
Usage:
55+
1. Place this script in the `Tools/gittools/pre_commit_copyright.py` file.
56+
2. Ensure the docstring of this file is set to the expected copyright notice.
57+
3. Add the entry to your `.pre-commit-config.yaml` file as shown above.
58+
4. Run `pre-commit install` to set up the pre-commit hooks.
59+
5. Now, when you try to commit changes, this hook will check for the copyright notice.
60+
6. If any files do not contain the expected copyright notice, the commit will be aborted.
61+
7. The error message will list all files that do not match the expected copyright notice.
62+
8. Use --ignore=file_path to exclude specific files from copyright checking.
63+
64+
Command line usage:
65+
python pre_commit_copyright.py file1.py file2.py --ignore=excluded_file.py \
66+
--ignore=another_excluded.py
67+
"""
68+
69+
70+
def get_file_paths() -> list[str]:
71+
"""Parse command line arguments and return a sorted list of file paths excluding
72+
ignored files."""
73+
parser = ArgumentParser(description="Check copyright notice in files")
74+
parser.add_argument("files", nargs="+", help="File paths to check")
75+
parser.add_argument(
76+
"--ignore",
77+
action="append",
78+
default=[],
79+
help="File paths to ignore (can be used multiple times)",
80+
)
81+
args = parser.parse_args()
82+
return sorted(set(args.files) - set(args.ignore))
83+
84+
85+
def check_copyright(file_path: Path) -> bool:
86+
"""Check if the file contains the expected copyright notice."""
87+
try:
88+
return __doc__ in file_path.read_text()
89+
except Exception as e: # noqa: BLE001 Path.read_text() can raise many exceptions
90+
print(f"Error reading {file_path}: {e}")
91+
return False
92+
93+
94+
def main():
95+
"""Main function to check all file paths from command line arguments.
96+
97+
pre-commit will repeatedly call this function and each time it will pass ~four file
98+
paths to be checked.
99+
100+
Supports --ignore flags to exclude specific files from copyright checking.
101+
"""
102+
if failed_files := [
103+
path for path in get_file_paths() if not check_copyright(Path(path))
104+
]:
105+
raise SystemExit(f"Copyright not found in: {', '.join(failed_files)}")
106+
107+
108+
if __name__ == "__main__":
109+
main()

tests/test_pre_commit_copyright.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import pytest
2+
from unittest.mock import patch
3+
4+
from Tools.gittools.pre_commit_copyright import get_file_paths
5+
6+
7+
def test_get_file_paths_no_args():
8+
"""Test get_file_paths with no command line arguments."""
9+
with patch('sys.argv', ["pre_commit_copyright.py"]):
10+
with pytest.raises(SystemExit, match="2"):
11+
_ = get_file_paths()
12+
13+
14+
@pytest.mark.parametrize(
15+
"argv, expected",
16+
[
17+
(
18+
["pre_commit_copyright.py", "file1.py", "file2.py", "file3.py"],
19+
["file1.py", "file2.py", "file3.py"]
20+
),
21+
(
22+
["pre_commit_copyright.py", "file1.py", "file2.py", "--ignore=file2.py"],
23+
["file1.py"]
24+
),
25+
(
26+
["pre_commit_copyright.py", "file1.py", "file2.py", "file3.py", "--ignore=file1.py", "--ignore=file3.py"],
27+
["file2.py"]
28+
),
29+
(
30+
["pre_commit_copyright.py", "file1.py", "file2.py", "--ignore=excluded_file.py", "--ignore=another_excluded.py"],
31+
["file1.py", "file2.py"]
32+
),
33+
(
34+
["pre_commit_copyright.py", "file1.py", "--ignore=file1.py"],
35+
[]
36+
),
37+
]
38+
)
39+
def test_get_file_paths_parametrized(argv, expected):
40+
"""Test get_file_paths with various command line argument combinations."""
41+
with patch('sys.argv', argv):
42+
assert get_file_paths() == expected

0 commit comments

Comments
 (0)