Skip to content

Commit f1a1de2

Browse files
nicoddemusbluetech
andauthored
Use manual trigger to prepare release PRs (#8150)
Co-authored-by: Ran Benita <[email protected]>
1 parent 950fbbc commit f1a1de2

File tree

3 files changed

+200
-0
lines changed

3 files changed

+200
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: prepare release pr
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
branch:
7+
description: 'Branch to base the release from'
8+
required: true
9+
default: ''
10+
major:
11+
description: 'Major release? (yes/no)'
12+
required: true
13+
default: 'no'
14+
15+
jobs:
16+
build:
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- uses: actions/checkout@v2
21+
with:
22+
fetch-depth: 0
23+
24+
- name: Set up Python
25+
uses: actions/setup-python@v2
26+
with:
27+
python-version: "3.8"
28+
29+
- name: Install dependencies
30+
run: |
31+
python -m pip install --upgrade pip
32+
pip install --upgrade setuptools tox
33+
34+
- name: Prepare release PR (minor/patch release)
35+
if: github.event.inputs.branch.major == 'no'
36+
run: |
37+
tox -e prepare-release-pr -- ${{ github.event.inputs.branch }} ${{ secrets.chatops }}
38+
39+
- name: Prepare release PR (major release)
40+
if: github.event.inputs.branch.major == 'yes'
41+
run: |
42+
tox -e prepare-release-pr -- ${{ github.event.inputs.branch }} ${{ secrets.chatops }} --major

scripts/prepare-release-pr.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
This script is part of the pytest release process which is triggered manually in the Actions
3+
tab of the repository.
4+
5+
The user will need to enter the base branch to start the release from (for example
6+
``6.1.x`` or ``master``) and if it should be a major release.
7+
8+
The appropriate version will be obtained based on the given branch automatically.
9+
10+
After that, it will create a release using the `release` tox environment, and push a new PR.
11+
12+
**Secret**: currently the secret is defined in the @pytestbot account,
13+
which the core maintainers have access to. There we created a new secret named `chatops`
14+
with write access to the repository.
15+
"""
16+
import argparse
17+
import re
18+
from pathlib import Path
19+
from subprocess import check_call
20+
from subprocess import check_output
21+
from subprocess import run
22+
23+
from colorama import Fore
24+
from colorama import init
25+
from github3.repos import Repository
26+
27+
28+
class InvalidFeatureRelease(Exception):
29+
pass
30+
31+
32+
SLUG = "pytest-dev/pytest"
33+
34+
PR_BODY = """\
35+
Created automatically from manual trigger.
36+
37+
Once all builds pass and it has been **approved** by one or more maintainers, the build
38+
can be released by pushing a tag `{version}` to this repository.
39+
"""
40+
41+
42+
def login(token: str) -> Repository:
43+
import github3
44+
45+
github = github3.login(token=token)
46+
owner, repo = SLUG.split("/")
47+
return github.repository(owner, repo)
48+
49+
50+
def prepare_release_pr(base_branch: str, is_major: bool, token: str) -> None:
51+
print()
52+
print(f"Processing release for branch {Fore.CYAN}{base_branch}")
53+
54+
check_call(["git", "checkout", f"origin/{base_branch}"])
55+
56+
try:
57+
version = find_next_version(base_branch, is_major)
58+
except InvalidFeatureRelease as e:
59+
print(f"{Fore.RED}{e}")
60+
raise SystemExit(1)
61+
62+
print(f"Version: {Fore.CYAN}{version}")
63+
64+
release_branch = f"release-{version}"
65+
66+
run(
67+
["git", "config", "user.name", "pytest bot"],
68+
text=True,
69+
check=True,
70+
capture_output=True,
71+
)
72+
run(
73+
["git", "config", "user.email", "[email protected]"],
74+
text=True,
75+
check=True,
76+
capture_output=True,
77+
)
78+
79+
run(
80+
["git", "checkout", "-b", release_branch, f"origin/{base_branch}"],
81+
text=True,
82+
check=True,
83+
capture_output=True,
84+
)
85+
86+
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.")
87+
88+
# important to use tox here because we have changed branches, so dependencies
89+
# might have changed as well
90+
cmdline = ["tox", "-e", "release", "--", version, "--skip-check-links"]
91+
print("Running", " ".join(cmdline))
92+
run(
93+
cmdline, text=True, check=True, capture_output=True,
94+
)
95+
96+
oauth_url = f"https://{token}:[email protected]/{SLUG}.git"
97+
run(
98+
["git", "push", oauth_url, f"HEAD:{release_branch}", "--force"],
99+
text=True,
100+
check=True,
101+
capture_output=True,
102+
)
103+
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} pushed.")
104+
105+
body = PR_BODY.format(version=version)
106+
repo = login(token)
107+
pr = repo.create_pull(
108+
f"Prepare release {version}", base=base_branch, head=release_branch, body=body,
109+
)
110+
print(f"Pull request {Fore.CYAN}{pr.url}{Fore.RESET} created.")
111+
112+
113+
def find_next_version(base_branch: str, is_major: bool) -> str:
114+
output = check_output(["git", "tag"], encoding="UTF-8")
115+
valid_versions = []
116+
for v in output.splitlines():
117+
m = re.match(r"\d.\d.\d+$", v.strip())
118+
if m:
119+
valid_versions.append(tuple(int(x) for x in v.split(".")))
120+
121+
valid_versions.sort()
122+
last_version = valid_versions[-1]
123+
124+
changelog = Path("changelog")
125+
126+
features = list(changelog.glob("*.feature.rst"))
127+
breaking = list(changelog.glob("*.breaking.rst"))
128+
is_feature_release = features or breaking
129+
130+
if is_major:
131+
return f"{last_version[0]+1}.0.0"
132+
elif is_feature_release:
133+
return f"{last_version[0]}.{last_version[1] + 1}.0"
134+
else:
135+
return f"{last_version[0]}.{last_version[1]}.{last_version[2] + 1}"
136+
137+
138+
def main() -> None:
139+
init(autoreset=True)
140+
parser = argparse.ArgumentParser()
141+
parser.add_argument("base_branch")
142+
parser.add_argument("token")
143+
parser.add_argument("--major", action="store_true", default=False)
144+
options = parser.parse_args()
145+
prepare_release_pr(
146+
base_branch=options.base_branch, is_major=options.major, token=options.token
147+
)
148+
149+
150+
if __name__ == "__main__":
151+
main()

tox.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ passenv = {[testenv:release]passenv}
157157
deps = {[testenv:release]deps}
158158
commands = python scripts/release-on-comment.py {posargs}
159159

160+
[testenv:prepare-release-pr]
161+
decription = prepare a release PR from a manual trigger in GitHub actions
162+
usedevelop = {[testenv:release]usedevelop}
163+
passenv = {[testenv:release]passenv}
164+
deps = {[testenv:release]deps}
165+
commands = python scripts/prepare-release-pr.py {posargs}
166+
160167
[testenv:publish-gh-release-notes]
161168
description = create GitHub release after deployment
162169
basepython = python3

0 commit comments

Comments
 (0)