Skip to main content

Pre-commit hook to check and auto-add copyright notices with multi-format and regex support

Project description

SNY Copyright Check - Pre-commit Hook

License: MIT Python 3.10+ PyPI version Tests

A powerful pre-commit hook to automatically check and add copyright notices to your source files with support for multiple file formats and regex patterns.

Features

โœจ Multi-Format Support: Define different copyright formats for different file extensions (.py, .c, .sql, etc.)

๐Ÿ” Regex Pattern Matching: Use regex patterns in your copyright templates to match year ranges (e.g., 2024, 2024-2026)

๐Ÿ”ง Auto-Fix: Automatically adds missing copyright notices with the current year

๐Ÿง  Smart Copyright Replacement: Intelligently replace similar existing copyrights with your standardized template

  • Uses similarity matching to identify copyrights from the same business entity
  • Preserves and merges year ranges (e.g., 2021-2024 โ†’ 2021-2026)
  • Handles variations in formatting, wording, and licensing details

๐Ÿ“ Flexible Templates: Section-based template file for easy maintenance

๐ŸŽฏ Smart Insertion: Respects shebang lines and file structure

๐Ÿ”„ Line Ending Preservation: Automatically detects and preserves CRLF (Windows) or LF (Unix/Linux) line endings

๐ŸŽฏ Git Integration: Check only changed files with --changed-only flag

โœ… Non-Destructive: Existing copyrights are preserved, even with old years - no duplicates created

๐ŸŽ“ Git-Aware Year Management: Intelligently manages copyright years based on Git history

  • Project-wide years (default): All files use project inception year (e.g., 2018-2026)
  • Per-file years: Each file uses its own creation year with --per-file-years flag
  • Preserves earliest year from file creation or existing copyright
  • Extends year range only when files are modified
  • Reduces noise in Git diffs by not updating unchanged files

๐Ÿšซ Ignore Files Support: Respect .copyrightignore and .gitignore patterns

  • Skip generated files, vendor code, and build artifacts
  • Gitignore-style pattern matching
  • Flexible ignore rules per project

๐Ÿ“ Hierarchical Copyright Templates: Support different copyrights per directory

  • Apply different copyright notices to subdirectories
  • Perfect for monorepos and vendor/third-party code
  • Child directories override parent templates

Installation

Via pip (Recommended)

Install directly from PyPI:

pip install sny-copyright-checker

As a pre-commit hook

Add the following to your .pre-commit-config.yaml:

repos:
  - repo: https://github.com/mu-triv/sny-copyright-checker
    rev: v1.0.6  # Use the latest release
    hooks:
      - id: sny-copyright-checker
        args: [--notice=copyright.txt]

Then install the hook:

pre-commit install

From source

git clone https://github.com/mu-triv/sny-copyright-checker.git
cd sny-copyright-checker
pip install -e .

Usage

As a Pre-commit Hook

Once installed, the hook will automatically run when you commit files:

git add my_file.py
git commit -m "Add new feature"
# Hook will check and add copyright notice if missing

Command Line

You can also run the tool directly:

# Check and auto-fix files
sny-copyright-checker file1.py file2.sql file3.c

# Check only (no modifications)
sny-copyright-checker --no-fix file1.py

# Replace similar existing copyrights with template version
sny-copyright-checker --replace file1.py file2.py

# Replace copyrights in all changed files
sny-copyright-checker --replace --changed-only

# Specify custom template file
sny-copyright-checker --notice=my_copyright.txt *.py

# Verbose output
sny-copyright-checker -v file1.py

# Check only changed files in git
sny-copyright-checker --changed-only

# Check changed files compared to main branch
sny-copyright-checker --changed-only --base-ref origin/main

Command Line Options

  • filenames: Files to check for copyright notices
  • --notice FILENAME: Filename of copyright template (default: copyright.txt). In hierarchical mode, this filename is searched for in the directory tree.
  • --fix: Automatically add missing copyright notices (default: enabled)
  • --no-fix: Only check without modifying files
  • --verbose, -v: Enable verbose output
  • --changed-only: Only check files that have been changed in git (ignores filenames argument)
  • --base-ref REF: Git reference to compare against when using --changed-only (default: HEAD)
  • --no-git-aware: Disable Git-aware year management (default: Git-aware is enabled)
  • --per-file-years: Use individual file creation years instead of project inception year (default: project-wide years)
  • --ignore-file PATH: Path to custom ignore file (default: auto-detect .copyrightignore)
  • --no-gitignore: Don't use .gitignore patterns (default: .gitignore is used)
  • --hierarchical: Enable hierarchical copyright templates (looks for --notice file in each directory)

Git-Aware Year Management

By default, the tool uses Git history to intelligently manage copyright years:

  • Preserves the earliest year from file creation or existing copyright
  • Extends the year range only when files are actually modified
  • Reduces unnecessary changes to unchanged files

See GIT_AWARE_YEAR_MANAGEMENT.md for detailed information.

Hierarchical Copyright Templates

Support different copyright notices for different parts of your codebase. Perfect for monorepos, projects with vendor code, or when different teams/components have different licensing.

For detailed documentation, see HIERARCHICAL_TEMPLATES.md.

Quick Start

Enable hierarchical mode and place copyright templates in subdirectories:

sny-copyright-check --hierarchical --notice=copyright.txt src/

Directory structure:

project/
โ”œโ”€โ”€ copyright.txt              # Root copyright
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ main.py               # Uses root copyright
โ””โ”€โ”€ vendor/
    โ”œโ”€โ”€ copyright.txt          # Vendor-specific copyright
    โ””โ”€โ”€ lib.py                # Uses vendor copyright

Ignore Files Support

The tool respects ignore patterns to skip generated files, vendor code, and build artifacts.

For detailed documentation on ignore patterns, see IGNORE_FILES.md.

Quick Start

Create a .copyrightignore file in your project root:

# Ignore generated files
**/generated/
*.min.js
*.bundle.js

# Ignore vendor/third-party code
node_modules/
vendor/
third_party/

# Ignore build artifacts
dist/
build/
*.pyc
__pycache__/

Using .gitignore

By default, patterns from .gitignore are also respected. To disable this:

sny-copyright-checker --no-gitignore *.py

Pattern Syntax

Supports standard gitignore-style patterns:

  • *.ext - Match files with extension
  • dir/ - Match entire directory
  • **/pattern - Match in any directory
  • path/*.js - Match files in specific path
  • # comment - Comments (ignored)

Copyright Template Format

The template file uses a section-based format with regex support and template variables for maximum maintainability.

Using Variables (Recommended)

Define common values once in a [VARIABLES] section and reuse them throughout your template:

[VARIABLES]
SPDX_LICENSE = MIT
COMPANY = Sony Group Corporation
AUTHOR = R&D Center Europe Brussels Laboratory, Sony Group Corporation
YEAR_PATTERN = {regex:\d{4}(-\d{4})?}

[.py, .yaml, .yml]
# SPDX-License-Identifier: {SPDX_LICENSE}
# Copyright {YEAR_PATTERN} {COMPANY}
# Author: {AUTHOR}
# License: For licensing see the License.txt file

[.js, .ts, .go, .rs]
// SPDX-License-Identifier: {SPDX_LICENSE}
// Copyright {YEAR_PATTERN} {COMPANY}
// Author: {AUTHOR}
// License: For licensing see the License.txt file

Benefits:

  • โœ… DRY Principle: Change company name or license in one place
  • โœ… SPDX Support: Easy to add SPDX License Identifiers
  • โœ… Consistency: All file types use the same information
  • โœ… Flexibility: Different projects can have different licenses

Traditional Format (Also Supported)

You can also use the format without variables:

[.py, .yaml, .yml]
# Copyright {regex:\d{4}(-\d{4})?} Sony Group Corporation
# Author: R&D Center Europe Brussels Laboratory, Sony Group Corporation
# License: For licensing see the License.txt file

Template Syntax

  • Variables Section: [VARIABLES] - Define reusable values
    • Format: VARIABLE_NAME = value
    • Use in templates: {VARIABLE_NAME}
    • Example: COMPANY = Sony Group Corporation, then use {COMPANY}
  • Section Headers:
    • Single extension: [.extension] (e.g., [.py], [.sql])
    • Multiple extensions: [.ext1, .ext2, .ext3] (e.g., [.js, .ts, .go])
    • All extensions in a grouped header will use the same copyright format
  • Regex Patterns: {regex:PATTERN} allows regex matching
    • Example: {regex:\d{4}(-\d{4})?} matches 2024 or 2024-2026
    • Can be stored in a variable: YEAR_PATTERN = {regex:\d{4}(-\d{4})?}
  • Auto-insertion: When adding a copyright, the regex pattern is replaced with the current year

SPDX License Identifiers

The tool fully supports SPDX license identifiers. Simply add them to your template:

[VARIABLES]
SPDX_LICENSE = Apache-2.0 OR MIT

[.py]
# SPDX-License-Identifier: {SPDX_LICENSE}
# Copyright {regex:\d{4}} {COMPANY}

Common SPDX identifiers: MIT, Apache-2.0, GPL-3.0-only, BSD-3-Clause, etc.

Grouping Extensions

To reduce duplication and improve maintainability, group extensions that share the same comment syntax:

  • Hash comments: [.py, .yaml, .yml, .sh] โ†’ # style
  • C-style line comments: [.js, .ts, .go, .rs, .java] โ†’ // style
  • C block comments: [.c, .h, .cpp] โ†’ /* */ style
  • SQL comments: [.sql] โ†’ -- style
  • HTML comments: [.md, .html, .xml] โ†’ <!-- --> style

Supported Regex Patterns

The template includes regex patterns for year matching:

  • \d{4}: Matches a single year (e.g., 2024)
  • \d{4}-\d{4}: Matches a year range (e.g., 2024-2026)
  • \d{4}(-\d{4})?: Matches either format

Example

Before

#!/usr/bin/env python

def hello():
    print("Hello, World!")

After (Auto-fixed)

#!/usr/bin/env python
# Copyright 2026 SNY Group Corporation
# Author: R&D Center Europe Brussels Laboratory, SNY Group Corporation
# License: For licensing see the License.txt file

def hello():
    print("Hello, World!")

How It Works

  1. Template Parsing: Reads and parses the section-based template file
  2. File Extension Matching: Determines which copyright template to use based on file extension
  3. Pattern Matching: Uses regex to check if a valid copyright notice exists
  4. Copyright Detection: If a valid copyright exists (even with an old year like 2025), the file is left unchanged
  5. Auto-Insertion: If missing and --fix is enabled, adds the copyright notice with the current year
  6. Smart Positioning: Respects shebang lines and inserts the notice appropriately
  7. Line Ending Preservation: Automatically detects and preserves the original line ending style (CRLF or LF)
  8. Git Integration: Optionally checks only files that have been modified in your git repository

File Extensions Support

By default, the included copyright.txt supports:

  • Python (.py)
  • SQL (.sql)
  • C/C++ (.c, .cpp, .h)
  • JavaScript/TypeScript (.js, .ts)
  • Java (.java)
  • Shell scripts (.sh)
  • Go (.go)
  • Rust (.rs)
  • YAML (.yaml, .yml)
  • Markdown (.md)

You can easily add more by editing the copyright.txt file.

Important Behavior

Copyright Preservation

  • Existing copyrights are never replaced: If a file already has a valid copyright notice (even from an old year like 2025), it will be left unchanged
  • No duplicates created: Running the tool multiple times will not create duplicate copyright notices
  • Year matching: The regex pattern {regex:\d{4}(-\d{4})?} matches any year or year range, ensuring old copyrights are recognized

Line Ending Handling

  • Automatic detection: The tool automatically detects whether your files use CRLF (Windows) or LF (Unix/Linux) line endings
  • Preservation: Original line endings are preserved when adding copyright notices
  • Cross-platform compatibility: Works seamlessly on Windows, Linux, and macOS

Git Integration

When using --changed-only:

  • Only files tracked by git and currently modified (staged or unstaged) are checked
  • Files must have a supported extension
  • Deleted files are automatically excluded
  • Works with files in subdirectories

Configuration

Customizing Templates

Edit your copyright.txt file to add or modify copyright formats:

[.your_extension]
Your copyright format here
With multiple lines
And {regex:\d{4}} for year matching

Pre-commit Configuration

Customize the pre-commit hook behavior with various arguments:

Basic Configuration

- repo: https://github.com/mu-triv/sny-copyright-checker
  rev: v1.0.6
  hooks:
    - id: sny-copyright-checker
      args: [--notice=copyright.txt]

Check Only (No Auto-fix)

Run the checker without automatically adding copyright notices:

- repo: https://github.com/mu-triv/sny-copyright-checker
  rev: v1.0.6
  hooks:
    - id: sny-copyright-checker
      args: [--no-fix, --notice=copyright.txt]

Verbose Output

Enable detailed output for debugging:

- repo: https://github.com/mu-triv/sny-copyright-checker
  rev: v1.0.6
  hooks:
    - id: sny-copyright-checker
      args: [--verbose, --notice=copyright.txt]

Check Only Changed Files

Check only files modified compared to a specific git reference:

- repo: https://github.com/mu-triv/sny-copyright-checker
  rev: v1.0.6
  hooks:
    - id: sny-copyright-checker
      args: [--changed-only, --base-ref=origin/main, --notice=copyright.txt]

Limit to Specific File Types

Only run on specific file types using the files regex:

- repo: https://github.com/mu-triv/sny-copyright-checker
  rev: v1.0.6
  hooks:
    - id: sny-copyright-checker
      args: [--notice=copyright.txt]
      files: \.(py|sql|c|cpp)$

Combined Configuration

Combine multiple options:

- repo: https://github.com/mu-triv/sny-copyright-checker
  rev: v1.0.6
  hooks:
    - id: sny-copyright-checker
      args: [--verbose, --notice=copyright.txt, --changed-only]
      files: \.(py|js|ts|java)$

Multiple Hooks with Different Configurations

Run separate hooks for different scenarios:

repos:
  - repo: https://github.com/mu-triv/sny-copyright-checker
    rev: v1.0.6
    hooks:
      # Auto-fix Python files only
      - id: sny-copyright-checker
        name: Copyright Check (Python - Auto-fix)
        args: [--notice=copyright.txt]
        files: \.py$

      # Check-only for C/C++ files
      - id: sny-copyright-checker
        name: Copyright Check (C/C++ - Check Only)
        args: [--no-fix, --notice=copyright.txt, --verbose]
        files: \.(c|cpp|h)$

Development

Setting Up Development Environment

git clone https://github.com/mu-triv/sny-copyright-checker.git
cd sny-copyright-checker
pip install -e .

Running Tests

# Run all tests
pytest tests/

# Run with coverage
pytest tests/ --cov=scripts --cov-report=term

# Run specific test file
pytest tests/test_copyright_checker.py -v

Continuous Integration

The project uses GitHub Actions for CI/CD:

  • Tests: Runs on Python 3.10-3.12 across Ubuntu, Windows, and macOS
  • Code Quality: Checks with flake8, black, and isort
  • Coverage: Generates test coverage reports

The workflow automatically runs on:

  • Every push to the main branch
  • Every pull request targeting main

Project Structure

sny-copyright-checker/
โ”œโ”€โ”€ scripts/
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ main.py                      # Entry point
โ”‚   โ”œโ”€โ”€ copyright_checker.py         # Main checker logic
โ”‚   โ””โ”€โ”€ copyright_template_parser.py # Template parser
โ”œโ”€โ”€ tests/                            # Test files
โ”œโ”€โ”€ .pre-commit-hooks.yaml           # Pre-commit hook definition
โ”œโ”€โ”€ .pre-commit-config.yaml          # Example pre-commit config
โ”œโ”€โ”€ copyright.txt                     # Example copyright template
โ”œโ”€โ”€ pyproject.toml                   # Project metadata
โ”œโ”€โ”€ setup.py                         # Setup script
โ”œโ”€โ”€ LICENSE                          # MIT License
โ””โ”€โ”€ README.md                        # This file

Comparison with Similar Tools

vs. copyright_notice_precommit

While inspired by leoll2/copyright_notice_precommit, this project adds:

  1. Multi-format support: Different copyright formats for different file types
  2. Regex matching: Flexible year range matching
  3. Auto-insertion: Automatically adds missing copyright notices
  4. Section-based templates: Easier to maintain multiple formats

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Running Tests

The project has comprehensive test coverage with 98+ test cases:

# Run all tests
pytest tests/

# Run specific test file
pytest tests/test_replace_feature.py -v

# Run with coverage report
pytest --cov=scripts tests/

# Run only fast tests (skip performance tests)
pytest tests/ -m "not slow"

Test Categories:

  • Unit tests: Core functionality (similarity, year extraction, entity matching)
  • Positive tests: Replacement scenarios with parametrized variations
  • Negative tests: Protection against unwanted replacements
  • Edge cases: Unicode, whitespace, different file types
  • Stress tests: Large files, batch processing, performance

The test suite uses pytest.mark.parametrize for efficient coverage of multiple scenarios.

Known Issues and Limitations

Template Changes May Create Duplicate Copyrights

Issue: When you update your copyright.txt template and re-run the checker on files that already have a copyright, it may add a second copyright notice instead of recognizing the existing one.

Why: The checker uses strict template matching. If the copyright format changes (e.g., different company name, license identifier, or text), the old copyright no longer matches the new template and is considered "missing."

Solution: Use the --replace flag to intelligently replace similar copyrights based on entity matching rather than exact template matching. See REPLACE_FEATURE.md for details.

Example Scenario:

# Original copyright (Company A)
# Copyright 2025 Company A
# License: MIT

def hello():
    pass

After updating copyright.txt to use Company B and using --replace:

# Copyright 2026 Company B      # <- Replaced (if entities match)
# SPDX-License-Identifier: Apache-2.0

def hello():
    pass

Or if entities don't match, both are preserved:

# Copyright 2026 Company B      # <- New copyright added
# SPDX-License-Identifier: Apache-2.0

# Copyright 2025 Company A      # <- Old copyright preserved (different entity)
# License: MIT

def hello():
    pass

Recommended Approaches:

  1. Use --replace flag: Intelligently replace similar copyrights (recommended)
    sny-copyright-check --replace --fix file.py
    
  2. Test first: Run in check-only mode to preview changes
    sny-copyright-check --replace --verbose --no-fix file.py
    
  3. Manual cleanup: Before changing templates, update existing copyrights
  4. Selective application: Use git to apply checker only to new/modified files

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Tri VU Khac (khactri.vu@sony.com)
SONY Group Corporation
R&D Center Europe Brussels Laboratory

Acknowledgments

Support

For issues, questions, or contributions, please visit the GitHub repository.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sny_copyright_checker-1.0.6.tar.gz (75.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sny_copyright_checker-1.0.6-py3-none-any.whl (26.5 kB view details)

Uploaded Python 3

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page