blob: 5a4df5dfab27c1e1e1f64b4cf7313033cd917011 [file] [log] [blame]
Dan Willemsenadad21e2022-03-25 17:22:05 -07001"""
2Finalize the repo for a release. Invokes towncrier and bumpversion.
3"""
4
5__requires__ = ['bump2version', 'towncrier']
6
7
8import subprocess
9import pathlib
10import re
11import sys
12
13
14def release_kind():
15 """
16 Determine which release to make based on the files in the
17 changelog.
18 """
19 # use min here as 'major' < 'minor' < 'patch'
20 return min(
21 'major' if 'breaking' in file.name else
22 'minor' if 'change' in file.name else
23 'patch'
24 for file in pathlib.Path('changelog.d').iterdir()
25 )
26
27
28bump_version_command = [
29 sys.executable,
30 '-m', 'bumpversion',
31 release_kind(),
32]
33
34
35def get_version():
36 cmd = bump_version_command + ['--dry-run', '--verbose']
37 out = subprocess.check_output(cmd, text=True)
38 return re.search('^new_version=(.*)', out, re.MULTILINE).group(1)
39
40
41def update_changelog():
42 cmd = [
43 sys.executable, '-m',
44 'towncrier',
45 'build',
46 '--version', get_version(),
47 '--yes',
48 ]
49 subprocess.check_call(cmd)
50 _repair_changelog()
51
52
53def _repair_changelog():
54 """
55 Workaround for #2666
56 """
57 changelog_fn = pathlib.Path('CHANGES.rst')
58 changelog = changelog_fn.read_text()
59 fixed = re.sub(r'^(v[0-9.]+)v[0-9.]+$', r'\1', changelog, flags=re.M)
60 changelog_fn.write_text(fixed)
61 subprocess.check_output(['git', 'add', changelog_fn])
62
63
64def bump_version():
65 cmd = bump_version_command + ['--allow-dirty']
66 subprocess.check_call(cmd)
67
68
69def ensure_config():
70 """
71 Double-check that Git has an e-mail configured.
72 """
73 subprocess.check_output(['git', 'config', 'user.email'])
74
75
76def check_changes():
77 """
78 Verify that all of the files in changelog.d have the appropriate
79 names.
80 """
81 allowed = 'deprecation', 'breaking', 'change', 'doc', 'misc'
82 except_ = 'README.rst', '.gitignore'
83 news_fragments = (
84 file
85 for file in pathlib.Path('changelog.d').iterdir()
86 if file.name not in except_
87 )
88 unrecognized = [
89 str(file)
90 for file in news_fragments
91 if not any(f".{key}" in file.suffixes for key in allowed)
92 ]
93 if unrecognized:
94 raise ValueError(f"Some news fragments have invalid names: {unrecognized}")
95
96
97if __name__ == '__main__':
98 print("Cutting release at", get_version())
99 ensure_config()
100 check_changes()
101 update_changelog()
102 bump_version()