8000 Merge the PR with one core dev approval and CI passed. (GH-51) · python/cpython@69f93c6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 69f93c6

Browse files
authored
Merge the PR with one core dev approval and CI passed. (GH-51)
Allow miss-islington to merge the PR if all of these conditions met: - All CI passed - At least one core dev approved - PR is made by miss-islington
1 parent 0a4af6f commit 69f93c6

File tree

5 files changed

+522
-37
lines changed

5 files changed

+522
-37
lines changed

backport/delete_branch.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ async def delete_branch(event, gh, *args, **kwargs):
1515
if event.data["pull_request"]["merged"]:
1616
issue_number = event.data['pull_request']['number']
1717
merged_by = event.data['pull_request']['merged_by']['login']
18-
util.comment_on_pr(issue_number, f"Thanks, @{merged_by}!")
18+
if merged_by != "miss-islington":
19+
util.comment_on_pr(issue_number, f"Thanks, @{merged_by}!")
20+
else:
21+
util.comment_on_pr(issue_number, "Thanks!")
1922

2023
branch_name = event.data['pull_request']['head']['ref']
2124
util.delete_branch(branch_name)

backport/status_change.py

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import requests
2-
import os
31
import re
42

5-
from gidgethub import sansio, routing
3+
from gidgethub import routing
64

75
from . import util
86

@@ -11,7 +9,6 @@
119
TITLE_RE = re.compile(r'\[(?P<branch>\d+\.\d+)\].+?(?P<pr>\d+)\)')
1210

1311

14-
1512
@router.register("status")
1613
async def check_status(event, gh, *args, **kwargs):
1714
"""
@@ -20,43 +17,86 @@ async def check_status(event, gh, *args, **kwargs):
2017
if event.data["commit"].get("committer") \
2118
and event.data["commit"]["committer"]["login"] == "miss-islington":
2219
sha = event.data["sha"]
23-
status_url = f"https://api.github.com/repos/python/cpython/commits/{sha}/status"
24-
request_headers = sansio.create_headers(
25-
"miss-islington",
26-
oauth_token=os.getenv('GH_AUTH'))
27-
response = requests.get(status_url,
28-
headers=request_headers)
29-
result = response.json()
30-
all_ci_status = [status["state"] for status in result["statuses"]]
31-
all_ci_context = [status["context"] for status in result["statuses"]]
32-
print(f"miss-islington's PR state changed: {all_ci_status}")
33-
print(f"miss-islington's PR CI: {all_ci_context}")
34-
if "pending" not in all_ci_status \
35-
and "continuous-integration/travis-ci/pr" in all_ci_context:
36-
url = "https://api.github.com/repos/miss-islington/cpython/git/refs/heads/"
37-
response = requests.get(url, headers=request_headers)
38-
for ref in response.json():
39-
if "backport-" in ref["ref"] and ref["object"]["sha"] == sha:
40-
backport_branch_name = ref["ref"].split("/")[-1]
41-
pr_url = f"https://api.github.com/repos/python/cpython/pulls?state=open&head=miss-islington:{backport_branch_name}"
42-
pr_response = requests.get(pr_url, headers=request_headers).json()
43-
if pr_response:
44-
pr_number = pr_response[0]["number"]
45-
normalized_pr_title = util.normalize_title(pr_response[0]["title"],
46-
pr_response[0]["body"])
47-
48-
title_match = TITLE_RE.match(normalized_pr_title)
49-
if title_match:
20+
await check_ci_status_and_approval(gh, sha, leave_comment=True)
21+
22+
23+
@router.register("pull_request_review", action="submitted")
24+
async def pr_reviewed(event, gh, *args, **kwargs):
25+
if event.data["pull_request"]["user"]["login"] == "miss-islington":
26+
reviewer = event.data["review"]["user"]["login"]
27+
approved = event.data["review"]["state"] == "approved"
28+
if approved and await util.is_core_dev(gh, reviewer):
29+
sha = event.data["review"]["commit_id"]
30+
await check_ci_status_and_approval(gh, sha)
31+
32+
33+
async def check_ci_status_and_approval(gh, sha, leave_comment=False):
34+
35+
result = await gh.getitem(f'/repos/python/cpython/commits/{sha}/status')
36+
all_ci_status = [status["state"] for status in result["statuses"]]
37+
all_ci_context = [status["context"] for status in result["statuses"]]
38+
39+
if "pending" not in all_ci_status \
40+
and "continuous-integration/travis-ci/pr" in all_ci_context:
41+
async for ref in gh.getiter('/repos/miss-islington/cpython/git/refs/heads/'):
42+
if "backport-" in ref["ref"] and ref["object"]["sha"] == sha:
43+
backport_branch_name = ref["ref"].split("/")[-1]
44+
async for pr_response in gh.getiter(f'/repos/python/cpython/pulls?state=open&head=miss-islington:{backport_branch_name}'):
45+
pr_number = pr_response["number"]
46+
normalized_pr_title = util.normalize_title(
47+
pr_response["title"],
48+
pr_response["body"])
49+
50+
title_match = TITLE_RE.match(normalized_pr_title)
51+
if title_match:
52+
53+
if leave_comment:
5054
original_pr_number = title_match.group('pr')
51-
original_pr_url = f"https://api.github.com/repos/python/cpython/pulls/{original_pr_number}"
52-
original_pr_result = requests.get(original_pr_url,
53-
headers=request_headers).json()
55+
original_pr_url = f"/repos/python/cpython/pulls/{original_pr_number}"
56+
original_pr_result = await gh.getitem(original_pr_url)
5457
pr_author = original_pr_result["user"]["login"]
5558
committer = original_pr_result["merged_by"]["login"]
5659

5760
participants = util.get_participants(
5861
pr_author, committer)
5962
emoji = "✅" if result['state'] == "success" else "❌"
60-
util.comment_on_pr(
61-
pr_number,
63+
64+
await comment_on_pr(gh,
65+
pr_number=pr_number,
6266
message=f"{participants}: Backport status check is done, and it's a {result['state']} {emoji} .")
67+
68+
if result['state'] == "success":
69+
async for review in gh.getiter(f"/repos/python/cpython/pulls/{pr_number}/reviews"):
70+
reviewer = review["user"]["login"]
71+
approved = review["state"].lower() == "approved"
72+
if approved \
73+
and await util.is_core_dev(gh, reviewer):
74+
await merge_pr(gh, pr_number, sha)
75+
break
76+
77+
78+
async def merge_pr(gh, pr_number, sha):
79+
async for commit in gh.getiter(f"/repos/python/cpython/pulls/{pr_number}/commits"):
80+
if commit["sha"] == sha:
81+
pr_commit_msg = commit["commit"]["message"].split("\n")
82+
83+
cleaned_up_title = f"{pr_commit_msg[0]} (GH-{pr_number})"
84+
await gh.put(f"/repos/python/cpython/pulls/{pr_number}/merge",
85+
data={"commit_title": cleaned_up_title,
86+
"commit_message": "\n".join(pr_commit_msg[1:]),
87+
"sha": sha,
88+
"merge_method": "squash"
89+
}
90+
)
91+
break
92+
93+
94+
async def comment_on_pr(gh, pr_number, message):
95+
"""
96+
Leave a comment on a PR/Issue
97+
"""
98+
issue_comment_url = f"/repos/python/cpython/issues/{pr_number}/comments"
99+
data = {
100+
"body": message,
101+
}
102+
await gh.post(issue_comment_url, data=data)

backport/util.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import os
33
import subprocess
44

5+
import gidgethub
6+
57
from gidgethub import sansio
68

79

@@ -70,3 +72,26 @@ def normalize_title(title, body):
7072
else:
7173
# Being paranoid in case \r\n is used.
7274
return title[:-1] + body[1:].partition('\r\n')[0]
75+
76+
# Copied over from https://github.com/python/bedevere
77+
async def is_core_dev(gh, username):
78+
"""Check if the user is a CPython core developer."""
79+
org_teams = "/orgs/python/teams"
80+
team_name = "python core"
81+
async for team in gh.getiter(org_teams):
82+
if team["name"].lower() == team_name:
83+
break
84+
else:
85+
raise ValueError(f"{team_name!r} not found at {org_teams!r}")
86+
# The 'teams' object only provides a URL to a deprecated endpoint,
87+
# so manually construct the URL to the non-deprecated team membership
88+
# endpoint.
89+
membership_url = f"/teams/{team['id']}/memberships/{username}"
90+
try:
91+
await gh.getitem(membership_url)
92+
except gidgethub.BadRequest as exc:
93+
if exc.status_code == 404:
94+
return False
95+
raise
96+
else:
97+
return True

0 commit comments

Comments
 (0)
0