#!/usr/bin/env python3 """Commit everything, tag the version of the newest changelog entry and push main with the tags. The tag is the version of the first entry of debian-package/debian/changelog, without the Debian revision 1: vpn-router (0.5.1-1) is tagged 0.5.1 and (0.5.1-2) 0.5.1-2. The tag starts the release workflow. """ import argparse import pathlib import shlex import subprocess import sys ROOT = pathlib.Path(__file__).resolve().parent CHANGELOG = ROOT / 'debian-package' / 'debian' / 'changelog' #: Print the git commands instead of running them; set by -n/--dry-run. DRY_RUN = False def git(*args): """Run git in the repository, or print the command on a dry run; stop with git's exit status when it fails.""" command = ['git', *args] if DRY_RUN: print(shlex.join(command)) return result = subprocess.run(command, cwd=ROOT) if result.returncode != 0: sys.exit(result.returncode) def summary(): """The summary line of the first changelog entry, or None when it has none. The summary is the entry's first text line: indented by two spaces, not a "*" item, with a blank line between it and the header and a blank line after it. """ lines = CHANGELOG.read_text(encoding='utf-8').splitlines() if len(lines) < 4 or lines[1].strip() or lines[3].strip(): return None line = lines[2] text = line.strip() if not line.startswith(' ') or line.startswith(' ') or not text or text.startswith('*') or line.startswith(' -- '): return None return text def version(): """The tag of the first changelog entry: its version, without the Debian revision when that is 1.""" first = CHANGELOG.read_text(encoding='utf-8').splitlines()[0] start, end = first.find('('), first.find(')') if start == -1 or end < start: sys.exit(f'{CHANGELOG}: the first line has no version: {first}') full = first[start + 1:end] upstream, _, revision = full.rpartition('-') return upstream if upstream and revision == '1' else full def main(): global DRY_RUN parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument('-m', '--message', help='commit message; default: the summary line of the newest changelog entry, a line without "*" between blank lines right after its header') parser.add_argument('-n', '--dry-run', action='store_true', help='print the git commands instead of running them') parser.add_argument('--no-push', action='store_true', help='commit and tag, but do not push') args = parser.parse_args() DRY_RUN = args.dry_run message = args.message or summary() if not message: sys.exit(f'no commit message: pass -m, or start the newest entry of {CHANGELOG} with a summary line ' 'without "*", indented by two spaces, with a blank line above and below it') tag = version() if subprocess.run(['git', 'rev-parse', '-q', '--verify', f'refs/tags/{tag}'], cwd=ROOT, capture_output=True).returncode == 0: sys.exit(f'tag {tag} exists; add a new changelog entry first') git('add', '-A') git('commit', '-m', message) git('tag', tag) if not args.no_push: git('push', 'origin', 'main', '--tags') return 0 if __name__ == '__main__': sys.exit(main())