Use manual trigger to prepare release PRs (#8150)
Co-authored-by: Ran Benita <ran@unusedvar.com>
This commit is contained in:
parent
950fbbc326
commit
f1a1de2257
|
@ -0,0 +1,42 @@
|
|||
name: prepare release pr
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Branch to base the release from'
|
||||
required: true
|
||||
default: ''
|
||||
major:
|
||||
description: 'Major release? (yes/no)'
|
||||
required: true
|
||||
default: 'no'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.8"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade setuptools tox
|
||||
|
||||
- name: Prepare release PR (minor/patch release)
|
||||
if: github.event.inputs.branch.major == 'no'
|
||||
run: |
|
||||
tox -e prepare-release-pr -- ${{ github.event.inputs.branch }} ${{ secrets.chatops }}
|
||||
|
||||
- name: Prepare release PR (major release)
|
||||
if: github.event.inputs.branch.major == 'yes'
|
||||
run: |
|
||||
tox -e prepare-release-pr -- ${{ github.event.inputs.branch }} ${{ secrets.chatops }} --major
|
|
@ -0,0 +1,151 @@
|
|||
"""
|
||||
This script is part of the pytest release process which is triggered manually in the Actions
|
||||
tab of the repository.
|
||||
|
||||
The user will need to enter the base branch to start the release from (for example
|
||||
``6.1.x`` or ``master``) and if it should be a major release.
|
||||
|
||||
The appropriate version will be obtained based on the given branch automatically.
|
||||
|
||||
After that, it will create a release using the `release` tox environment, and push a new PR.
|
||||
|
||||
**Secret**: currently the secret is defined in the @pytestbot account,
|
||||
which the core maintainers have access to. There we created a new secret named `chatops`
|
||||
with write access to the repository.
|
||||
"""
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
from subprocess import check_call
|
||||
from subprocess import check_output
|
||||
from subprocess import run
|
||||
|
||||
from colorama import Fore
|
||||
from colorama import init
|
||||
from github3.repos import Repository
|
||||
|
||||
|
||||
class InvalidFeatureRelease(Exception):
|
||||
pass
|
||||
|
||||
|
||||
SLUG = "pytest-dev/pytest"
|
||||
|
||||
PR_BODY = """\
|
||||
Created automatically from manual trigger.
|
||||
|
||||
Once all builds pass and it has been **approved** by one or more maintainers, the build
|
||||
can be released by pushing a tag `{version}` to this repository.
|
||||
"""
|
||||
|
||||
|
||||
def login(token: str) -> Repository:
|
||||
import github3
|
||||
|
||||
github = github3.login(token=token)
|
||||
owner, repo = SLUG.split("/")
|
||||
return github.repository(owner, repo)
|
||||
|
||||
|
||||
def prepare_release_pr(base_branch: str, is_major: bool, token: str) -> None:
|
||||
print()
|
||||
print(f"Processing release for branch {Fore.CYAN}{base_branch}")
|
||||
|
||||
check_call(["git", "checkout", f"origin/{base_branch}"])
|
||||
|
||||
try:
|
||||
version = find_next_version(base_branch, is_major)
|
||||
except InvalidFeatureRelease as e:
|
||||
print(f"{Fore.RED}{e}")
|
||||
raise SystemExit(1)
|
||||
|
||||
print(f"Version: {Fore.CYAN}{version}")
|
||||
|
||||
release_branch = f"release-{version}"
|
||||
|
||||
run(
|
||||
["git", "config", "user.name", "pytest bot"],
|
||||
text=True,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
run(
|
||||
["git", "config", "user.email", "pytestbot@gmail.com"],
|
||||
text=True,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
run(
|
||||
["git", "checkout", "-b", release_branch, f"origin/{base_branch}"],
|
||||
text=True,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.")
|
||||
|
||||
# important to use tox here because we have changed branches, so dependencies
|
||||
# might have changed as well
|
||||
cmdline = ["tox", "-e", "release", "--", version, "--skip-check-links"]
|
||||
print("Running", " ".join(cmdline))
|
||||
run(
|
||||
cmdline, text=True, check=True, capture_output=True,
|
||||
)
|
||||
|
||||
oauth_url = f"https://{token}:x-oauth-basic@github.com/{SLUG}.git"
|
||||
run(
|
||||
["git", "push", oauth_url, f"HEAD:{release_branch}", "--force"],
|
||||
text=True,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} pushed.")
|
||||
|
||||
body = PR_BODY.format(version=version)
|
||||
repo = login(token)
|
||||
pr = repo.create_pull(
|
||||
f"Prepare release {version}", base=base_branch, head=release_branch, body=body,
|
||||
)
|
||||
print(f"Pull request {Fore.CYAN}{pr.url}{Fore.RESET} created.")
|
||||
|
||||
|
||||
def find_next_version(base_branch: str, is_major: bool) -> str:
|
||||
output = check_output(["git", "tag"], encoding="UTF-8")
|
||||
valid_versions = []
|
||||
for v in output.splitlines():
|
||||
m = re.match(r"\d.\d.\d+$", v.strip())
|
||||
if m:
|
||||
valid_versions.append(tuple(int(x) for x in v.split(".")))
|
||||
|
||||
valid_versions.sort()
|
||||
last_version = valid_versions[-1]
|
||||
|
||||
changelog = Path("changelog")
|
||||
|
||||
features = list(changelog.glob("*.feature.rst"))
|
||||
breaking = list(changelog.glob("*.breaking.rst"))
|
||||
is_feature_release = features or breaking
|
||||
|
||||
if is_major:
|
||||
return f"{last_version[0]+1}.0.0"
|
||||
elif is_feature_release:
|
||||
return f"{last_version[0]}.{last_version[1] + 1}.0"
|
||||
else:
|
||||
return f"{last_version[0]}.{last_version[1]}.{last_version[2] + 1}"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
init(autoreset=True)
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("base_branch")
|
||||
parser.add_argument("token")
|
||||
parser.add_argument("--major", action="store_true", default=False)
|
||||
options = parser.parse_args()
|
||||
prepare_release_pr(
|
||||
base_branch=options.base_branch, is_major=options.major, token=options.token
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
7
tox.ini
7
tox.ini
|
@ -157,6 +157,13 @@ passenv = {[testenv:release]passenv}
|
|||
deps = {[testenv:release]deps}
|
||||
commands = python scripts/release-on-comment.py {posargs}
|
||||
|
||||
[testenv:prepare-release-pr]
|
||||
decription = prepare a release PR from a manual trigger in GitHub actions
|
||||
usedevelop = {[testenv:release]usedevelop}
|
||||
passenv = {[testenv:release]passenv}
|
||||
deps = {[testenv:release]deps}
|
||||
commands = python scripts/prepare-release-pr.py {posargs}
|
||||
|
||||
[testenv:publish-gh-release-notes]
|
||||
description = create GitHub release after deployment
|
||||
basepython = python3
|
||||
|
|
Loading…
Reference in New Issue