Skip to main content

reuseify

PyPi Python version REUSE status Coverage Status

Automate REUSE license annotation from git history.

reuseify inspects which files are missing license headers (via reuse lint), looks up their git commit authors, and applies reuse annotate, all from a single CLI.

Installation

uv pip install .

Usage

reuseify annotate and reuseify lint both require a reuseify.toml policy file: it is the only source of the copyright/license used to annotate and lint files. There's no --copyright/--license CLI flag; if reuseify.toml doesn't exist yet, running either command creates an empty one and tells you what to add to it.

cat > reuseify.toml <<'EOF'
[default]
copyright = "2025 X-Men"
license = "MIT"
EOF

reuseify annotate

When no reuse_annotate_authors.json file is present, annotate looks up authors from git history directly, no separate step needed.

If you want to inspect or hand-edit the author list before annotating (or cache it for reuse across runs), use the two-step workflow instead: collect authors → annotate files.

Step 1 (optional): collect authors

reuseify get-authors [OPTIONS]

Runs reuse lint, finds every file missing a license header, looks up its git commit authors, and writes a JSON file:

{
  "src/foo.py": ["Alice", "Bob"],
  "src/bar.c":  ["Alice"],
  "src/new.py": [] #NOT_IN_GIT
}
Option Short Default Description
--output -o reuse_annotate_authors.json Output JSON file
--include-not-in-git -i off Include files with no git history (empty author list)
--exclude PATTERN -e Extra glob pattern to exclude (matched per path component, repeatable)

Files matching built-in patterns are always excluded: __pycache__, .venv, venv, .env, env, .git, .vscode, .idea, *.egg-info, *.pyc, dist, build, node_modules, .tox, .mypy_cache, .pytest_cache, .ruff_cache.

Files ignored by .gitignore are also excluded automatically.

Examples

# defaults
reuseify get-authors

# custom output path + include untracked files
reuseify get-authors --output authors.json --include-not-in-git

# add an extra exclusion pattern
reuseify get-authors --exclude reports --exclude "*.tmp"

Step 2: annotate files

reuseify annotate [OPTIONS] [REUSE ANNOTATE FLAGS...]

Requires a reuseify.toml; it's created empty if missing (see above). --copyright/--license are not accepted as CLI flags: they're always resolved per file from reuseify.toml, and a file whose matched rule/[default] doesn't specify both just fails with a clear message, instead of falling back to a CLI value that could disagree with what other files in the project use.

If --input (or its default, reuse_annotate_authors.json) exists, reads authors from that JSON file. Otherwise, looks up authors from git history directly, the same lookup Step 1 does, so running get-authors first is optional. Either way, --contributor is injected automatically per file, plus any extra names from --contributor on the command line (see below). All unrecognised flags are forwarded verbatim to reuse annotate, giving you full control over --year, --style, --fallback-dot-license, --force-dot-license, --skip-unrecognised, etc.

Option Short Default Description
--input -i reuse_annotate_authors.json JSON file from get-authors, if present; otherwise authors are read from git history directly
--contributor NAME -n none Extra contributor(s) (repeatable). In batch mode, added on top of each file's discovered authors and also covers NOT_IN_GIT files. In direct-file mode, the only source of contributors
--default-contributor NAME -d none Fallback contributor for NOT_IN_GIT files (repeatable), used when --contributor isn't given
--download / --no-download -D on Download missing license files (reuse download --all) after annotating

Output is grouped: all successes first, then skips, then failures, then finally a summary.

License downloading runs automatically after every annotate; pass --no-download to skip it. A download failure is printed in red but never fails the annotate run itself, since annotating the files is the point of the command and a missing LICENSES/*.txt is a separate, recoverable problem.

Passing --input explicitly with a file that doesn't exist is still treated as an error (it fails fast and points you at get-authors), since that's almost always a typo rather than a request to fall back to git history.

Examples

# basic (copyright/license come from reuseify.toml)
reuseify annotate --fallback-dot-license

# custom input + fallback contributor for untracked files
reuseify annotate \
    --input authors.json \
    --default-contributor "Charles Xavier"

# multiple default contributors
reuseify annotate \
    --default-contributor "Professor X" \
    --default-contributor "Cyclops"

Check compliance: lint

reuseify lint [OPTIONS]

Runs reuse lint to find git-tracked files missing a REUSE header or referencing a license whose text isn't in LICENSES/. Requires a reuseify.toml (see below; created empty if missing) and also checks that each governed file's actual declared license and copyright match its assigned rule, not just that some valid header is present. Files matching no rule and no [default] are still checked for a REUSE header, just not for a specific license/copyright.

Option Short Default Description
--include-not-in-git -i off Include files with no git history in the check
--exclude PATTERN -e none Extra glob pattern to exclude (matched per path component, repeatable)

Exit codes: 0 compliant, 1 REUSE or policy violations found, 2 an underlying reuse/reuseify tool failure (never treated as "compliant").

reuseify.toml: per-path license policy

reuseify.toml at the project root is required by annotate/lint; it's the only source of the copyright/license used to annotate and lint files, and it also turns reuseify lint into a stricter check: it verifies each governed file has the correct license for its path, not just some valid license. Projects with a single license across the whole tree just need a [default]; use [[rules]] too for projects with more than one.

[[rules]]
paths = ["src/**"]
copyright = "Sahil Jhawar"
license = "GPL-3.0-or-later"

[[rules]]
paths = ["vendor/**", "third_party/**"]
copyright = "Some Vendor"
license = "MIT"

# a rule matching an exact file always wins over a directory glob
[[rules]]
paths = ["vendor/special_file.py"]
copyright = "Sahil Jhawar"
license = "GPL-3.0-or-later"

[default]
copyright = "Sahil Jhawar"
license = "GPL-3.0-or-later"
  • [[rules]]: paths is a glob (or list of globs) matched against the file's git-relative path. When a file matches more than one rule, the most specific one wins: an exact file path beats a directory glob, and among glob patterns, the longer one wins.
  • [default]: used for any tracked file that matches no rule. Files matching neither a rule nor [default] are not governed by the policy and are ignored by both lint and annotate.
  • Leave the year out of copyright ("Sahil Jhawar", not "2026 Sahil Jhawar"): reuse annotate always prepends the current year itself, so including one produces a duplicated year in the header.

Each file is annotated with its matched rule's values automatically:

reuseify get-authors
reuseify annotate --default-contributor "Charles Xavier"

A file whose matched rule leaves a field unset falls through to [default] for that field; if neither specifies it, that file fails with a clear message rather than silently picking up some other value.

Annotating specific files directly

Pass one or more file paths on the command line to annotate just those files, skipping the reuse lint/git-history discovery step entirely. There's no author auto-detection for files named this way, so use --contributor (repeatable) to set one; it's optional, same as in reuse annotate itself, so omitting it just annotates without a --contributor line. --copyright/--license still resolve from reuseify.toml as above.

reuseify annotate --contributor "Sahil Jhawar" src/main.py

paths glob matching uses fnmatch, which is case-sensitive on POSIX/macOS and case-insensitive on Windows. reuseify only targets POSIX/Unix/macOS (see the classifiers in pyproject.toml), so write patterns case-exact with forward slashes.

Pre-commit hook

reuseify ships a pre-commit hook that runs reuseify lint and fails the commit if any git-tracked file is missing a REUSE license header.

Add this to your .pre-commit-config.yaml:

repos:
  - repo: https://github.com/sahiljhawar/reuseify
    rev: 1.1.0  # use the latest tag
    hooks:
      - id: reuseify-lint

Then install it once per clone:

pre-commit install

Disclaimer

reuse annotate --help

This project is not affiliated with the REUSE project or its maintainers in any way.

Metadata

Release files for reuseify 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for reuseify 1.1.0
File Size Uploaded
reuseify-1.1.0.tar.gz 38.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for reuseify 1.1.0
File Interpreter ABI Platform
reuseify-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 71.2 kB

Release files / reuseify-1.1.0.tar.gz

Download URL reuseify-1.1.0.tar.gz
Size 38.0 kB
Tags Source
SHA-256 checksum
How to use checksums
3d9660e78b0d9f1d4770ea0ebe6fb83999ab45dbc6873be5c0ab10419800d351
BLAKE2b-256 checksum
How to use checksums
20c73e7bfc0cb61097c90c7922f753908935c6ecfc362e954575f5b57e27ab71
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / reuseify-1.1.0-py3-none-any.whl

Download URL reuseify-1.1.0-py3-none-any.whl
Size 33.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5ec24085203e764f71fd56ca530e2c2b1369d229f63057adfffb8cbf879418b4
BLAKE2b-256 checksum
How to use checksums
67b35067bcbd41859b27293a4bb1bae0188a77584a755e00309ba794b6516935
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page