Files
inbuxa-server/tools/fork/notice-check.py
T
jcoffey-dev 966241070e
ci / fork-checks (pull_request) Skipped
ci / build (pull_request) Skipped
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 6m29s
Name Coffey Labs LLC as the copyright holder
Coffey Labs is now Coffey Labs LLC, an Arizona limited liability company. Copyright lines and SPDX-FileCopyrightText headers naming Coffey Labs or John Coffey now name Coffey Labs LLC. Upstream copyright notices are unchanged.
2026-10-05 23:33:00 -07:00

109 lines
4.1 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 Coffey Labs LLC
# SPDX-License-Identifier: AGPL-3.0-only
"""
Fail when an upstream file the fork changed doesn't say so (AGPL section 5(a)).
tools/fork/notice-check.py # check; exit 1 on a missing notice
tools/fork/notice-check.py --fix # add the notice where it's missing
The AGPL asks a modified work to carry a prominent notice that it was
modified, with a date. Every upstream file this fork changes carries one
beneath upstream's own notice:
* Modified by Coffey Labs in 2026 for INBUXA.
"Changed" is measured against the newest stripped snapshot this tree has
merged, a commit on the `upstream` branch (docs/spec/SPEC.md §2.2a), so the
list is what actually differs rather than a guess. It's found in the tree's
own history, by the strip's commit subject, not taken from the branch head:
a branch that merges a new release is checked against it, and every other
branch against the release it's built on, whenever the `upstream` branch
moves. A file counts as
upstream's when its header names Stalwart Labs as a copyright holder; files
the fork wrote carry their own copyright and need nothing. Files with no
comment header at all (README, manifests) are covered by the README's prose.
"""
import argparse
import datetime
import re
import subprocess
import sys
HEADER_LINES = 15
UPSTREAM_HOLDER = re.compile(r'SPDX-FileCopyrightText:.*Stalwart Labs')
NOTICE = re.compile(r'Modified by Coffey Labs in \d{4}')
LICENSE_LINE = re.compile(r'^(\s*(?:\*|//|#)\s*)SPDX-License-Identifier:.*$')
def git(*args):
return subprocess.run(['git', *args], check=True, capture_output=True, text=True).stdout
def snapshot_ref():
ref = git('log', '-1', '--format=%h', '--grep=^Import upstream v',
'--grep=^Re-import upstream v', 'HEAD').strip()
if not ref:
sys.exit('notice-check: no upstream snapshot in this history; fetch it in full first')
return ref
def changed_upstream_files(ref):
# Against the working tree, not HEAD, so it also checks work not yet
# committed; in CI the two are the same.
for path in git('diff', '--name-only', '--diff-filter=M', ref).splitlines():
try:
head = open(path, encoding='utf-8').read().split('\n')[:HEADER_LINES]
except (OSError, UnicodeDecodeError):
continue
if any(UPSTREAM_HOLDER.search(line) for line in head):
yield path, head
def add_notice(path):
"""Put the notice under the license line, in that comment's own style."""
lines = open(path, encoding='utf-8').read().split('\n')
for n, line in enumerate(lines[:HEADER_LINES]):
m = LICENSE_LINE.match(line)
if m:
prefix = m.group(1)
blank = prefix.rstrip()
year = datetime.date.today().year
lines[n + 1:n + 1] = [blank, f'{prefix}Modified by Coffey Labs in {year} for INBUXA.']
open(path, 'w', encoding='utf-8').write('\n'.join(lines))
return True
return False
def main():
ap = argparse.ArgumentParser(description=__doc__.split('\n\n')[0].strip())
ap.add_argument('--fix', action='store_true', help='add the notice to every file missing it')
args = ap.parse_args()
ref = snapshot_ref()
checked, missing = 0, []
for path, head in changed_upstream_files(ref):
checked += 1
if not any(NOTICE.search(line) for line in head):
missing.append(path)
if args.fix:
unfixable = [p for p in missing if not add_notice(p)]
for p in sorted(set(missing) - set(unfixable)):
print(f'added: {p}')
missing = unfixable
if missing:
print(f'{len(missing)} upstream file(s) changed against {ref} without the modification notice:\n')
for p in missing:
print(f' {p}')
print('\nAdd "Modified by Coffey Labs in <year> for INBUXA." under the license line, '
'or run tools/fork/notice-check.py --fix.')
return 1
print(f'notice check: clean ({checked} changed upstream file(s), all marked; against {ref}).')
return 0
if __name__ == '__main__':
sys.exit(main())