1#!/usr/bin/env python3
2"""Updates version.yml and CHANGELOG.md for next version, then
3launch the `git citool` GUI to create a commit of it.
4"""
5
6import os
7import re
8import sys
9from contextlib import contextmanager
10from typing import Generator
11from shutil import which
12import argparse
13import subprocess
14
15from lib import re_ga, re_ea, get_gh_repo, vX, vY
16from lib.uiutil import Checker, CheckResult, run, check_command
17from lib.uiutil import run_txtcapture as run_capture
18from lib.start_release_updates import update_changelog_date
19from lib.gitutil import branch_exists, has_open_pr
20
21
22PR_MESSAGE = """
23All commits that are going out in {next_ver} release **must** be on {pr_branch}.
24This will allow the appropriate CI to run.
25
26Reviewers, please review the changelog and commits in this branch to make sure everything that needs to go out in the release is on this branch.
27"""
28
29
30def main(next_ver: str, commit: bool = True) -> int:
31 """Starts a release train.
32 This script will create a changelog entry and create PRs of applicable.
33 """
34 m = re_ga.match(next_ver)
35 if not m:
36 m = re_ea.match(next_ver)
37 assert m
38
39 if which("gh") is None:
40 print("gh tool is not installed.")
41 print("Please install the tool and rerun this script:")
42 print("https://github.com/cli/cli#installation")
43 return 1
44
45 if not os.getenv("GITHUB_TOKEN"):
46 run(["gh", "auth", "login"])
47
48 print()
49 print(f'Doing basic updates for v{next_ver}...')
50 print()
51
52 # This context manager and check function are pretty much just to produce
53 # a nice list of steps...
54
55 checker = Checker()
56
57 @contextmanager
58 def check(name: str) -> Generator[CheckResult, None, None]:
59 with checker.check(name) as subcheck:
60 yield subcheck
61 workbranch = ''
62 base_branch = ''
63 current_release_branch = f"rel/v{next_ver}"
64 release_branch = f"release/v{m[vX]}.{m[vY]}"
65 check_command(["git", "fetch", "--all"])
66
67 workbranch = current_release_branch
68 base_branch = release_branch
69 if branch_exists(current_release_branch):
70 with check(f"Checking out {current_release_branch}"):
71 run(["git", "checkout", current_release_branch])
72
73 if branch_exists("origin/" + current_release_branch):
74 with check(f"Bringing our branch it up to date with origin/{release_branch}"):
75 run(["git", "pull", "origin", current_release_branch])
76 else:
77 with check(f"Creating {current_release_branch}"):
78 run(["git", "checkout", "-b", current_release_branch])
79 if not checker.ok:
80 return 1
81 run(["git", "merge", f"origin/{release_branch}"])
82 with check(f"Checking for clean branch with no conflicts..."):
83 out = run_capture(["git", "status", "--porcelain"])
84 if out:
85 raise AssertionError(f"Merge conflicts on {current_release_branch}. Resolve these, then rerun this scrip")
86 if not checker.ok:
87 return 1
88
89 with check(f"Updating CHANGELOG.md with {next_ver}..."):
90 update_changelog_date(next_ver)
91 if not checker.ok:
92 return 1
93 if not commit:
94 print('not committing')
95 return 0
96
97 out = run_capture(["git", "status", "--porcelain"])
98 if not out:
99 print("No changes needed, all good.")
100 return 0
101
102 with check(f"Committing changes..."):
103 gitdir = run_capture(['git', 'rev-parse', '--git-dir'])
104 with open(os.path.join(gitdir, 'GITGUI_MSG'), 'w') as msgfile:
105 msgfile.write(f"Update for v{next_ver}\n")
106 if os.getenv("AMBASSADOR_RELENG_NO_GUI"):
107 run(['git', 'commit', '-am', f'Update for v{next_ver}'])
108 else:
109 run(['git', 'citool'])
110 run(["git", "push", "-u", "origin", workbranch])
111 if not checker.ok:
112 return 1
113 if has_open_pr(get_gh_repo(), base_branch, current_release_branch):
114 run(["gh", "pr", "view", workbranch, "--repo", get_gh_repo()])
115 return 0
116 pr_message = f"[v{next_ver}] Release Branch"
117 pr_body = PR_MESSAGE.format(next_ver=next_ver, pr_branch=current_release_branch)
118 if not checker.ok:
119 return 1
120
121 with check(f"Creating PR for {workbranch}"):
122 run(["gh", "pr", "create",
123 "--repo", get_gh_repo(),
124 "--base", base_branch,
125 "--title", pr_message,
126 "--body", pr_body])
127
128 if checker.ok:
129 if commit:
130 print(f'Update complete. CI triggered on the PR will generate RC artifacts to test')
131 print(f'Pushes to this branch will generate new RCs')
132 if os.getenv("AMBASSADOR_RELENG_NO_GUI"):
133 run(["gh", "pr", "view", workbranch, "--repo", get_gh_repo()])
134 else:
135 run(["gh", "pr", "view", workbranch, "--repo", get_gh_repo(), "--web"])
136 return 0
137 else:
138 return 1
139
140
141if __name__ == '__main__':
142 parser = argparse.ArgumentParser(description='Start a release train')
143
144 parser.add_argument('--next-version', help='Next version to prep for', required=True)
145 parser.add_argument('--no-commit', dest='commit', action='store_false', default=True)
146
147 args = parser.parse_args()
148
149 if not re_ga.match(args.next_version) and not re_ea.match(args.next_version):
150 sys.stderr.write(f"{os.path.basename(sys.argv[0])}: Version must match X.Y.Z(-ea)?\n")
151 sys.exit(2)
152
153 sys.exit(main(
154 next_ver=args.next_version,
155 commit=args.commit
156 ))
View as plain text