3 from pathlib
import Path
8 from github
import Github
12 from rich
import print
14 from util
import Spinner
16 default_branch_name =
"master"
19 return git(
"rev-parse",
"--abbrev-ref",
"HEAD").strip()
22 version_ex = re.compile(
r"^v?(\d+)\.(\d{1,2})\.(\d{1,2})$")
23 m = version_ex.match(version)
24 assert m
is not None, f
"Version {version} is not in valid format"
25 return tuple((int(m.group(i))
for i
in range(1, 4)))
28 return "v{:d}.{:>2d}.{:>02d}".format(*version)
31 with
Spinner(f
"Checking for {branch} branch"):
32 all_branches = [l.strip()
for l
in git.branch(all=
True, _tty_out=
False).strip().split(
"\n")]
33 for b
in all_branches:
34 if b.endswith(branch):
39 @click.option(
"--token",
"-T", required=
True, envvar=
"GITHUB_TOKEN")
40 @click.option(
"--repository",
"-R", required=
True, envvar=
"GITHUB_REPOSITORY")
41 @click.option(
"--retry", default=3)
43 def main(ctx, token, repository, retry):
44 gh = Github(token, retry=retry)
45 repo = gh.get_repo(repository)
53 return click.confirm(*args, **kwargs)
56 @click.argument(
"tag_name")
57 @click.option(
"--remote", default=
"origin")
58 @click.option(
"--yes",
"-y", is_flag=
True, default=
False)
60 def tag(obj, tag_name, remote, yes):
62 remote_url = git.remote(
"get-url", remote).strip()
69 major, minor, fix = tag
71 with
Spinner(f
"Checking for milestone for tag {tag_name}"):
73 for ms
in repo.get_milestones(state=
"all"):
74 if ms.title == tag_name:
77 assert tag_milestone
is not None,
"Did not find milestone for tag"
79 release_branch_name = f
"release/v{major}.{minor:>02}.X"
81 with
Spinner(
"Refreshing branches"):
82 git.fetch(all=
True, prune=
True)
86 with
Spinner(f
"Checking out and updating {default_branch_name}"):
87 git.checkout(default_branch_name)
91 assert not check_branch_exists(release_branch_name),
"For new minor: release branch CANNOT exist yet"
93 with
Spinner(f
"Creating {release_branch_name}"):
94 git.checkout(
"-b", release_branch_name)
98 with
Spinner(f
"Checking out {release_branch_name}"):
99 git.checkout(release_branch_name)
103 version_file = Path(
"version_number")
104 assert version_file.exists(),
"Version number file not found"
106 current_version_string = version_file.read_text()
107 print(f
"Current version: [bold]{current_version_string}[/bold]")
110 assert current_version_string ==
"9.9.9",
"Unexpected current version string found"
112 assert current_version_string != f
"{major}.{minor}.{fix-1}",
"Unexpected current version string found"
114 version_string = f
"{major}.{minor}.{fix}"
115 with
Spinner(f
"Bumping version number in '{version_file}' to '{version_string}'"):
116 with version_file.open(
"w")
as fh:
117 fh.write(version_string)
120 git.add(version_file)
121 git.commit(m=f
"Bump version number to {version_string}")
123 with
Spinner(f
"Creating tag {tag_name}"):
126 print(f
"I will now: push tag [bold green]{tag_name}[/bold green] and branch [bold green]{release_branch_name}[/bold green] to [bold]{remote_url}[/bold]")
127 if not confirm(
"Continue?", yes=yes):
128 raise SystemExit(
"Aborting")
130 with
Spinner(f
"Pushing branch {release_branch_name}"):
131 git.push(
"-u", remote, release_branch_name)
133 with
Spinner(f
"Pushing tag {tag_name}"):
134 git.push(remote, tag_name)
138 @click.argument(
"tag_name")
139 @click.option(
"--draft/--publish", default=
True)
140 @click.option(
"--yes",
"-y", is_flag=
True, default=
False)
142 def notes(obj, tag_name, draft, yes):
145 label_file = repo.get_contents(
".labels.yml", ref=default_branch_name).decoded_content
146 labels = yaml.safe_load(io.BytesIO(label_file))[
"labels"]
148 with
Spinner(f
"Finding tag {tag_name}"):
150 for t
in repo.get_tags():
151 if t.name == tag_name:
154 assert tag
is not None,
"Did not find tag"
156 with
Spinner(f
"Loading milestone for tag {tag_name}"):
158 for ms
in repo.get_milestones(state=
"all"):
159 if ms.title == tag_name:
162 assert tag_milestone
is not None,
"Did not find milestone for tag"
164 with
Spinner(f
"Getting PRs for milestone {tag_milestone.title}"):
168 "", milestone=tag_milestone.title, repo=repo.full_name, type=
"pr", **{
"is":
"merged"}
173 [pr.state ==
"open" for pr
in prs]
174 ),
"PRs assigned to milestone that are still open!"
176 click.echo(
"Have " + click.style(str(len(prs)), bold=
True) +
" PRs, all closed.")
180 groups = {l: []
for l
in sorted(labels)}
181 groups[
"Uncategorized"] = []
184 pr_labels = [l.name
for l
in pr.labels]
188 if label
in pr_labels:
189 groups[label].append(pr)
193 groups[
"Uncategorized"].append(pr)
195 for group, prs
in groups.items():
199 if name.lower() ==
"bug":
201 body += f
"#### {name}:\n\n"
203 body += f
"- {pr.title} [#{pr.number}]({pr.html_url})\n"
208 width, _ = click.get_terminal_size()
212 "\n".join([l.ljust(width)
for l
in [
""] + body.split(
"\n") + [
""]]),
219 with
Spinner(
"Getting release"):
221 release = repo.get_release(tag.name)
223 except github.UnknownObjectException:
226 if release
is not None:
230 "Existing release {} is at {}".format(
231 click.style(release.title, bold=
True),
232 click.style(release.html_url, bold=
True),
235 if confirm(f
"Update release {release.title}?", yes=yes):
236 with
Spinner(f
"Updating release {release.title}"):
237 release.update_release(name=release.title, message=body)
239 "Updated release is at {}".format(
240 click.style(release.html_url, bold=
True)
246 if confirm(f
"Create release for tag {tag.name} (draft: {draft})?", yes=yes):
247 with
Spinner(f
"Creating release {tag.name}"):
248 release = repo.create_git_release(
249 tag=tag.name, name=tag.name, message=body, draft=draft
252 "Created release is at {}".format(
253 click.style(release.html_url, bold=
True)
257 print(
"Not creating a release")
259 if tag_milestone.state ==
"open":
260 if confirm(f
"Do you want me to close milestone {tag_milestone.title}?", yes=yes):
261 with
Spinner(f
"Closing milestone {tag_milestone.title}"):
262 tag_milestone.edit(title=tag_milestone.title, state=
"closed")
264 print(
"Not closing milestone")