89 lines
3.3 KiB
Python
Executable File
89 lines
3.3 KiB
Python
Executable File
#!/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())
|