1#!/usr/bin/env python3
2"""Updates the dates in the changelog to today's date, then
3launch the `git citool` GUI to create a commit of it, and uses
4the `gh` tool to file a pull request.
5To be run after the GA version of emissary has been tagged.
6change the appropriate files, then launch the `git citool` GUI to
7create a commit of it.
8"""
9
10from os import getenv
11import datetime
12import fileinput
13import os
14import re
15import sys
16from contextlib import contextmanager
17from typing import Generator
18from shutil import which
19
20from lib import base_version, build_version, git_add, git_check_clean, re_ga, re_ea, vX, vY, get_gh_repo
21from lib.uiutil import Checker, CheckResult, run
22from lib.uiutil import run_txtcapture as run_capture
23from lib import ansiterm
24from lib.gitutil import has_open_pr
25
26
27def main(next_ver: str, today: datetime.date, quiet: bool=False, commit: bool = True) -> int:
28 """This edits several files (the Git directory "tree"), then launches
29 the `git citool` GUI to commit them. This _should_ be an utterly
30 trivial and readable list of
31 for line in fileinput.FileInput("FILENAME", inplace=True):
32 # edit 'line' as appropriate
33 sys.stdout.write(line)
34 git_add("FILENAME")
35 blocks. However, the block to edit the releaseNotes.yml file is unfortunately
36 more complex, given the structuring of YAML... maybe line-oriented processing
37 wasn't the best choice for that file, but parsing and reconstructing at as YAML
38 probably isn't great either. Hmmmm.
39 """
40
41 if not quiet:
42 print()
43 print(f'Doing basic updates for v{next_ver}...')
44 print()
45
46 if which("gh") is None:
47 print("gh tool is not installed.")
48 print("Please install the tool and rerun this script:")
49 print("https://github.com/cli/cli#installation")
50 return 1
51
52 warning = """
53 ==> Warning: FIXME: This script does not have the property that if
54 something goes wrong, you can just restart it; put another way:
55 it does not have the property that each step is idempotent.
56 If something does go wrong, then you'll have to address it, then
57 resume where the script left off by going through the checklist
58 manually (or by commenting out the already-completed steps).
59"""
60 print(f"{ansiterm.sgr.fg_red}{warning}{ansiterm.sgr}")
61
62 # This context manager and check function are pretty much just to produce
63 # a nice list of steps...
64 checker = Checker()
65
66 @contextmanager
67 def check(name: str) -> Generator[CheckResult, None, None]:
68 with checker.check(name) as subcheck:
69 yield subcheck
70
71 if not getenv("GIT_TOKEN"):
72 run(["gh", "auth", "login"])
73
74 m = re_ga.match(next_ver)
75 if not m:
76 m = re_ea.match(next_ver)
77 assert m
78 release_branch = f"release/v{m[vX]}.{m[vY]}"
79 with check(f"Checking that {release_branch} exists"):
80 run(["git", "rev-parse", "--verify", release_branch])
81 if not checker.ok:
82 checker.ok = True
83 with check(f"Creating {release_branch}"):
84 run(["git", "checkout", "-b", release_branch])
85 run(["git", "push", "-u", "origin", release_branch])
86 if not checker.ok:
87 print(f"Could not create {release_branch}")
88 print("Please create this branch then rerun this script")
89 return 1
90
91 user = os.getenv("USER")
92 if user is None or user == '':
93 user = 'unknownuser'
94 workbranch = f"{user}/v{next_ver}/changelog-updates"
95 with check(f"Creating new branch {workbranch}"):
96 if getenv('CI') == '':
97 run(["git", "fetch", "--tags"])
98 run(["git", "checkout", f"v{next_ver}", "-b", workbranch])
99 if not checker.ok:
100 return 1
101
102 with check(f"Updating releaseNotes.yml with date for {next_ver}..."):
103 state = 0
104
105 for line in fileinput.FileInput("docs/releaseNotes.yml", inplace=True):
106 if state == 0:
107 if line.strip() == f"- version: {next_ver}":
108 state = 1
109 elif state == 1:
110 if m := re.match(r"(\s+date:)\s+.*$", line):
111 line = f"{m.group(1)} '{today.strftime('%Y-%m-%d')}'\n"
112 state = 2
113
114 sys.stdout.write(line)
115
116 if state != 2:
117 # Something didn't go right.
118 sadness = f"""
119 ==> ERROR: could not find the date for version '{next_ver}' in
120 docs/releaseNotes.yml. Make sure that the block for that release
121 starts with
122
123 - version: {next_ver}
124 date: ....
125
126 as the start of the block."""
127
128 raise Exception(sadness)
129
130 # Once this is done, update the CHANGELOG from the release notes.
131 with check(f"Updating CHANGELOG.md from releaseNotes.yml..."):
132 run([ "make", f"{os.getcwd()}/CHANGELOG.md" ])
133
134 if checker.ok and commit:
135 with check(f"Committing changes..."):
136 gitdir = run_capture(['git', 'rev-parse', '--git-dir'])
137 with open(os.path.join(gitdir, 'GITGUI_MSG'), 'w') as msgfile:
138 msgfile.write(f"Update for v{next_ver}\n")
139 if getenv("AMBASSADOR_RELENG_NO_GUI"):
140 run(['git', 'commit', '-am', f'Update for v{next_ver}'])
141 else:
142 run(['git', 'citool'])
143 run(["git", "push", "-u", "origin", workbranch])
144 if not checker.ok:
145 return 1
146
147 with check(f"Creating PR for branch {workbranch}"):
148 # TODO: dont hardcode owners
149 pr_body = """Changelog date updates for {next_ver}
150
151Reviewers: The only changes in this PR should be updating the changelog entry for {next_ver} to the date it was released.
152
153If there are any other changes, the PR creator should note the reason."""
154 curr_release_branch = f"rel/v{next_ver}"
155 target_branch = release_branch
156 if has_open_pr(get_gh_repo(), release_branch, curr_release_branch):
157 target_branch = curr_release_branch
158 run(["gh", "pr", "create",
159 "--repo", get_gh_repo(),
160 "--base", target_branch,
161 "--title", f"[v{next_ver}] Changelog date update",
162 "--body", pr_body.format(next_ver=next_ver),
163 "--reviewer", "kflynn,rhs,esmet,acookin"])
164
165 if checker.ok:
166 if not getenv("AMBASSADOR_RELENG_NO_GUI"):
167 run(["gh", "pr", "view", workbranch, "--repo", get_gh_repo(), "--web"])
168 return 0
169 else:
170 return 1
171
172
173if __name__ == '__main__':
174 args = sys.argv[1:]
175
176 quiet = False
177 commit = True
178
179 while args and args[0].startswith("--"):
180 if args[0] == '--quiet':
181 quiet = True
182 args.pop(0)
183 elif args and (args[0] == '--no-commit'):
184 commit = False
185 args.pop(0)
186
187 if len(args) != 1 or (not re_ga.match(args[0]) and not re_ea.match(args[0])):
188 sys.stderr.write(f"Usage: {os.path.basename(sys.argv[0])} X.Y.Z\n")
189 sys.exit(2)
190
191 sys.exit(main(
192 next_ver=args[0],
193 today=datetime.date.today(),
194 quiet=quiet,
195 commit=commit,
196 ))
View as plain text