Skip to content
Closed
26 changes: 26 additions & 0 deletions .github/workflows/backport.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Backport

on:
pull_request:
types: [opened, reopened, edited]

env:
GH_AUTH: ${{ secrets.GH_AUTH }}

jobs:
backport:
name: Check and update backport label of PR

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
cache: pip
cache-dependency-path: "*requirements.txt"
- name: Install Dependencies
run: python3 -m pip install coverage -U pip -r dev-requirements.txt
- name: Run code
run: python3 -m bedevere.backport
26 changes: 26 additions & 0 deletions .github/workflows/close_pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Close PR

on:
pull_request:
types: [opened, synchronize]

env:
GH_AUTH: ${{ secrets.GH_AUTH }}

jobs:
close_pr:
name: close invalid PR

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
cache: pip
cache-dependency-path: "*requirements.txt"
- name: Install Dependencies
run: python3 -m pip install coverage -U pip -r dev-requirements.txt
- name: Run close_pr.py file
run: python3 -m bedevere.close_pr
26 changes: 26 additions & 0 deletions .github/workflows/filepaths.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Filepaths

on:
pull_request:
types: [opened, reopened, edited]

env:
GH_AUTH: ${{ secrets.GH_AUTH }}

jobs:
filepaths:
name: Checks filepaths on a PR

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
cache: pip
cache-dependency-path: "*requirements.txt"
- name: Install Dependencies
run: python3 -m pip install coverage -U pip -r dev-requirements.txt
- name: Run filepaths.py file
run: python3 -m bedevere.filepaths
26 changes: 26 additions & 0 deletions .github/workflows/review_pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Review PR

on:
pull_request:
types: [review_requested]

env:
GH_AUTH: ${{ secrets.GH_AUTH }}

jobs:
review_invalid_pr:
name: Dismiss review request from the invalid PR

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
cache: pip
cache-dependency-path: "*requirements.txt"
- name: Install Dependencies
run: python3 -m pip install coverage -U pip -r dev-requirements.txt
- name: Run review_pr.py file
run: python3 -m bedevere.review_pr
78 changes: 6 additions & 72 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
@@ -1,83 +1,17 @@
Contributing and Maintenance Guide
==================================

Bedevere web service is deployed to Heroku, which is managed by The PSF.
Bedevere web service is launched with GitHub Actions, which is managed by The PSF.

Deployment
----------
All actions are listed in the `Actions`_ tab of this repository.

There are two ways to have bedevere deployed: automatic deployment, and
manual deployment.

Automatic Deployment (currently broken)
'''''''''''''''''''''''''''''''''''''''

When the automatic deployment is enabled (on Heroku side), any merged PR
will automatically be deployed to Heroku. This process takes less than 5 minutes.

If after 10 minutes you did not see the changes reflected, please ping one
of the collaborators listed below.

To enable Automatic deployment:

- On the Heroku dashboard for bedevere, choose the "Deploy" tab.
- Scroll down to the "Automatic deploys" section
- Enter the name of the branch to be deployed (in this case: ``main``)
- Check the "Wait for CI to pass before deploy" button
- Press the "Enable automatic deploys" button.

Once done, merging a PR against the ``main`` branch will trigger a new
deployment using a webhook that is already set up in the repo settings.


.. note::

Due to recent `security incident`_, the Heroku GitHub integration is broken.
Automatic deployment does not currently work. Until this gets resolved,
maintainers have to deploy bedevere to Heroku manually.


Manual Deployment
'''''''''''''''''

The app can be deployed manually to Heroku by collaborators and members of the ``bedevere`` app on Heroku.
Heroku admins can do it too.

#. Install Heroku CLI

Details at: https://devcenter.heroku.com/articles/heroku-cli

#. Login to Heroku CLI on the command line and follow instructions::

heroku login


#. If you haven't already, get a clone of the bedevere repo::

git clone git@github.com:python/bedevere.git

Or, using `GitHub CLI`_::

gh repo clone python/bedevere

#. From the ``bedevere`` directory, add the ``bedevere`` Heroku app as remote branch::

heroku git:remote -a bedevere


#. From the ``bedevere`` directory, push to Heroku::

git push heroku main


After a successful push, the deployment will begin.

Heroku app collaborators and members
''''''''''''''''''''''''''''''''''''
App collaborators and members
'''''''''''''''''''''''''''''

- @Mariatta
- @ambv
- @brettcannon
- @sabderemane

.. _security incident: https://status.heroku.com/incidents/2413
.. _GitHub CLI: https://cli.github.com/
.. _Actions: https://github.com/python/bedevere/actions
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ This bot is meant to help identify issues with a CPython pull request.
- ### Identifies missing GitHub issue numbers in the title
If no GitHub issue number is found the status fails and the
"Details" link points to the relevant
[section of the devguide](https://devguide.python.org/pullrequest/#submitting).
- ### Links to bugs.python.org
[section of the devguide](https://devguide.python.org/getting-started/pull-request-lifecycle/#submitting).
- ### Links to github.com/python/cpython/issues
If an issue number is found then the "Details" link points to the relevant issue
itself, making it easier to navigate from PR to issue.
- ### Identifies missing news entry
Expand Down
66 changes: 43 additions & 23 deletions bedevere/backport.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
"""Automatically remove a backport label, and check backport PR validity."""
import asyncio
import functools
import json
import os
import re
import traceback

import gidgethub.routing
import aiohttp
from gidgethub.aiohttp import GitHubAPI

from . import util

create_status = functools.partial(util.create_status, 'bedevere/maintenance-branch-pr')


router = gidgethub.routing.Router()

TITLE_RE = re.compile(r'\s*\[(?P<branch>\d+\.\d+)\].+\((?:GH-|#)(?P<pr>\d+)\)', re.IGNORECASE)
MAINTENANCE_BRANCH_TITLE_RE = re.compile(r'\s*\[(?P<branch>\d+\.\d+)\].+')
MAINTENANCE_BRANCH_RE = re.compile(r'\s*(?P<branch>\d+\.\d+)')
BACKPORT_LABEL = 'needs backport to {branch}'
MESSAGE_TEMPLATE = ('[GH-{pr}](https://github.com/python/cpython/pull/{pr}) is '
'a backport of this pull request to the '
'[{branch} branch](https://github.com/python/cpython/tree/{branch}).')
BACKPORT_TITLE_DEVGUIDE_URL = "https://devguide.python.org/core-developers/committing/#backport-pr-title"


async def issue_for_PR(gh, pull_request):
"""Get the issue data for a pull request."""
return await gh.getitem(pull_request["issue_url"])
Comment on lines +26 to +28
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this copied from util.py?


BACKPORT_TITLE_DEVGUIDE_URL = "https://devguide.python.org/committing/#backport-pr-title"

async def _copy_over_labels(gh, original_issue, backport_issue):
"""Copy over relevant labels from the original PR to the backport PR."""
Expand All @@ -44,12 +50,12 @@ async def _remove_backport_label(gh, original_issue, branch, backport_pr_number)
await gh.post(original_issue['comments_url'], data={'body': message})


@router.register("pull_request", action="opened")
@router.register("pull_request", action="edited")
async def manage_labels(event, gh, *args, **kwargs):
if event.data["action"] == "edited" and "title" not in event.data["changes"]:
async def manage_labels(gh, *args, **kwargs):
with open(os.environ["GITHUB_EVENT_PATH"]) as f:
event = json.load(f)
if event.get("action") == "edited" and "title" not in event.get("changes"):
return
pull_request = event.data["pull_request"]
pull_request = event["pull_request"]
title = util.normalize_title(pull_request['title'],
pull_request['body'])
title_match = TITLE_RE.match(title)
Expand All @@ -58,12 +64,12 @@ async def manage_labels(event, gh, *args, **kwargs):
branch = title_match.group('branch')
original_pr_number = title_match.group('pr')

original_issue = await gh.getitem(event.data['repository']['issues_url'],
original_issue = await gh.getitem(event['repository']['issues_url'],
{'number': original_pr_number})
await _remove_backport_label(gh, original_issue, branch,
event.data["number"])
event["number"])

backport_issue = await util.issue_for_PR(gh, pull_request)
backport_issue = await issue_for_PR(gh, pull_request)
await _copy_over_labels(gh, original_issue, backport_issue)


Expand All @@ -82,21 +88,19 @@ def is_maintenance_branch(ref):
return bool(re.fullmatch(maintenance_branch_pattern, ref))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this got accidentally deleted during the merge (like the re.IGNORECASE above).



@router.register("pull_request", action="opened")
@router.register("pull_request", action="reopened")
@router.register("pull_request", action="edited")
@router.register("pull_request", action="synchronize")
async def validate_maintenance_branch_pr(event, gh, *args, **kwargs):
async def validate_maintenance_branch_pr(gh, *args, **kwargs):
"""Check the PR title for maintenance branch pull requests.

If the PR was made against maintenance branch, and the title does not
match the maintenance branch PR pattern, then post a failure status.

The maintenance branch PR has to start with `[X.Y]`
"""
if event.data["action"] == "edited" and "title" not in event.data["changes"]:
with open(os.environ["GITHUB_EVENT_PATH"]) as f:
event = json.load(f)
if event.get("action") == "edited" and "title" not in event.get("changes"):
return
pull_request = event.data["pull_request"]
pull_request = event["pull_request"]
base_branch = pull_request["base"]["ref"]

if not is_maintenance_branch(base_branch):
Expand All @@ -116,8 +120,7 @@ async def validate_maintenance_branch_pr(event, gh, *args, **kwargs):
await util.post_status(gh, event, status)


@router.register("create", ref_type="branch")
async def maintenance_branch_created(event, gh, *args, **kwargs):
async def maintenance_branch_created(gh, *args, **kwargs):
"""Create the `needs backport label` when the maintenance branch is created.

Also post a reminder to add the maintenance branch to the list of
Expand All @@ -128,7 +131,9 @@ async def maintenance_branch_created(event, gh, *args, **kwargs):

The maintenance branch PR has to start with `[X.Y]`
"""
branch_name = event.data["ref"]
with open(os.environ["GITHUB_EVENT_PATH"]) as f:
event = json.load(f)
branch_name = event["pull_request"]["head"]["ref"]

if MAINTENANCE_BRANCH_RE.match(branch_name):
await gh.post(
Expand All @@ -147,3 +152,18 @@ async def maintenance_branch_created(event, gh, *args, **kwargs):
),
},
)


async def main():
try:
async with aiohttp.ClientSession() as session:
gh = GitHubAPI(session, "sabderemane", oauth_token=os.getenv("GH_AUTH"))
await maintenance_branch_created(gh)
await validate_maintenance_branch_pr(gh)
await manage_labels(gh)
except Exception:
traceback.print_exc()


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Loading