Skip to content

Commit 3215042

Browse files
authored
Automerge PR labeled with "automerge". (#146)
Merge the PR if: - PR has "awaiting merge" and "automerge" labels, and - all CI status checks have passed. It will: - replace `#` with `GH-` - get the commit message from PR title and description Closes python/bedevere#14 Closes python/core-workflow#29
1 parent 85c6a4e commit 3215042

File tree

5 files changed

+499
-53
lines changed

5 files changed

+499
-53
lines changed

miss_islington/backport_pr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ async def backport_pr(event, gh, *args, **kwargs):
4242
if random.random() < 0.1:
4343
easter_egg = EASTER_EGG
4444
thanks_to = ""
45-
if created_by == merged_by:
45+
if created_by == merged_by or merged_by == "miss-islington":
4646
thanks_to = f"Thanks @{created_by} for the PR 🌮🎉."
4747
else:
4848
thanks_to = f"Thanks @{created_by} for the PR, and @{merged_by} for merging it 🌮🎉."

miss_islington/status_change.py

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,44 @@ async def check_status(event, gh, *args, **kwargs):
1414
"""
1515
Check the state change
1616
"""
17+
sha = event.data["sha"]
18+
1719
if (
1820
event.data["commit"].get("committer")
1921
and event.data["commit"]["committer"]["login"] == "miss-islington"
2022
):
21-
sha = event.data["sha"]
2223
await check_ci_status_and_approval(gh, sha, leave_comment=True)
24+
else:
25+
pr_for_commit = await util.get_pr_for_commit(gh, sha)
26+
if pr_for_commit:
27+
pr_labels = pr_for_commit["labels"]
28+
if util.pr_is_automerge(pr_labels) and util.pr_is_awaiting_merge(pr_labels):
29+
await check_ci_status_and_approval(
30+
gh, sha, leave_comment=True, is_automerge=True
31+
)
2332

2433

2534
@router.register("pull_request", action="labeled")
2635
async def pr_reviewed(event, gh, *args, **kwargs):
27-
if event.data["pull_request"]["user"]["login"] == "miss-islington":
28-
if util.pr_is_awaiting_merge(event.data["pull_request"]["labels"]):
29-
sha = event.data["pull_request"]["head"]["sha"]
30-
await check_ci_status_and_approval(gh, sha)
36+
37+
pr_labels = event.data["pull_request"]["labels"]
38+
39+
if util.pr_is_automerge(pr_labels) and util.pr_is_awaiting_merge(pr_labels):
40+
sha = event.data["pull_request"]["head"]["sha"]
41+
42+
await check_ci_status_and_approval(gh, sha, is_automerge=True)
43+
elif event.data["pull_request"]["user"][
44+
"login"
45+
] == "miss-islington" and util.pr_is_awaiting_merge(
46+
event.data["pull_request"]["labels"]
47+
):
48+
sha = event.data["pull_request"]["head"]["sha"]
49+
await check_ci_status_and_approval(gh, sha)
3150

3251

33-
async def check_ci_status_and_approval(gh, sha, leave_comment=False):
52+
async def check_ci_status_and_approval(
53+
gh, sha, leave_comment=False, is_automerge=False
54+
):
3455

3556
result = await gh.getitem(f"/repos/python/cpython/commits/{sha}/status")
3657
all_ci_status = [status["state"] for status in result["statuses"]]
@@ -40,56 +61,66 @@ async def check_ci_status_and_approval(gh, sha, leave_comment=False):
4061
"pending" not in all_ci_status
4162
and "continuous-integration/travis-ci/pr" in all_ci_context
4263
):
43-
44-
prs_for_commit = await gh.getitem(f'/search/issues?q=type:pr+repo:python/cpython+sha:{sha}')
45-
if prs_for_commit["total_count"] > 0: # there should only be one
46-
pr_for_commit = prs_for_commit["items"][0]
64+
pr_for_commit = await util.get_pr_for_commit(gh, sha)
65+
if pr_for_commit:
4766
pr_number = pr_for_commit["number"]
4867
normalized_pr_title = util.normalize_title(
4968
pr_for_commit["title"], pr_for_commit["body"]
5069
)
5170

5271
title_match = TITLE_RE.match(normalized_pr_title)
53-
if title_match:
72+
if title_match or is_automerge:
5473
if leave_comment:
55-
original_pr_number = title_match.group("pr")
56-
original_pr_url = (
57-
f"/repos/python/cpython/pulls/{original_pr_number}"
58-
)
59-
original_pr_result = await gh.getitem(original_pr_url)
60-
pr_author = original_pr_result["user"]["login"]
61-
committer = original_pr_result["merged_by"]["login"]
74+
if is_automerge:
75+
participants = await util.get_gh_participants(gh, pr_number)
76+
else:
77+
original_pr_number = title_match.group("pr")
78+
participants = await util.get_gh_participants(
79+
gh, original_pr_number
80+
)
6281

63-
participants = util.get_participants(pr_author, committer)
6482
emoji = "✅" if result["state"] == "success" else "❌"
6583

6684
await util.leave_comment(
6785
gh,
6886
pr_number=pr_number,
69-
message=f"{participants}: Backport status check is done, and it's a {result['state']} {emoji} .",
87+
message=f"{participants}: Status check is done, and it's a {result['state']} {emoji} .",
7088
)
71-
7289
if result["state"] == "success":
73-
pr = await gh.getitem(
74-
f"/repos/python/cpython/pulls/{pr_number}"
75-
)
76-
if util.pr_is_awaiting_merge(pr["labels"]):
77-
await merge_pr(gh, pr_number, sha)
7890

91+
if util.pr_is_awaiting_merge(pr_for_commit["labels"]):
92+
await merge_pr(
93+
gh, pr_for_commit, sha, is_automerge=is_automerge
94+
)
7995

80-
async def merge_pr(gh, pr_number, sha):
96+
97+
async def merge_pr(gh, pr, sha, is_automerge=False):
98+
pr_number = pr["number"]
8199
async for commit in gh.getiter(f"/repos/python/cpython/pulls/{pr_number}/commits"):
82100
if commit["sha"] == sha:
83-
pr_commit_msg = commit["commit"]["message"].split("\n")
84-
85-
cleaned_up_title = f"{pr_commit_msg[0]}"
86-
await gh.put(
87-
f"/repos/python/cpython/pulls/{pr_number}/merge",
88-
data={
89-
"commit_title": cleaned_up_title,
90-
"commit_message": "\n".join(pr_commit_msg[1:]),
91-
"sha": sha,
92-
"merge_method": "squash",
93-
},
94-
)
101+
if is_automerge:
102+
pr_commit_msg = pr["body"]
103+
pr_title = f"{pr['title']} (GH-{pr_number})"
104+
await gh.put(
105+
f"/repos/python/cpython/pulls/{pr_number}/merge",
106+
data={
107+
"commit_title": pr_title,
108+
"commit_message": pr_commit_msg,
109+
"sha": sha,
110+
"merge_method": "squash",
111+
},
112+
)
113+
else:
114+
pr_commit_msg = commit["commit"]["message"].split("\n")
115+
116+
cleaned_up_title = f"{pr_commit_msg[0]}"
117+
await gh.put(
118+
f"/repos/python/cpython/pulls/{pr_number}/merge",
119+
data={
120+
"commit_title": cleaned_up_title,
121+
"commit_message": "\n".join(pr_commit_msg[1:]),
122+
"sha": sha,
123+
"merge_method": "squash",
124+
},
125+
)
95126
break

miss_islington/util.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
from gidgethub import sansio
88

99

10+
AUTOMERGE_LABEL = ":robot: automerge"
11+
12+
1013
def comment_on_pr(issue_number, message):
1114
"""
1215
Leave a comment on a PR/Issue
@@ -66,9 +69,27 @@ def is_cpython_repo():
6669
return True
6770

6871

72+
async def get_gh_participants(gh, pr_number):
73+
pr_url = f"/repos/python/cpython/pulls/{pr_number}"
74+
pr_result = await gh.getitem(pr_url)
75+
created_by = pr_result["user"]["login"]
76+
77+
merged_by = None
78+
if pr_result["merged_by"] and pr_result["merged_by"]["login"] != "miss-islington":
79+
merged_by = pr_result["merged_by"]["login"]
80+
81+
participants = ""
82+
if created_by == merged_by or merged_by is None:
83+
participants = f"@{created_by}"
84+
else:
85+
participants = f"@{created_by} and @{merged_by}"
86+
87+
return participants
88+
89+
6990
def get_participants(created_by, merged_by):
7091
participants = ""
71-
if created_by == merged_by:
92+
if created_by == merged_by or merged_by == "miss-islington":
7293
participants = f"@{created_by}"
7394
else:
7495
participants = f"@{created_by} and @{merged_by}"
@@ -113,3 +134,20 @@ def pr_is_awaiting_merge(pr_labels):
113134
if label["name"] == "awaiting merge":
114135
return True
115136
return False
137+
138+
139+
def pr_is_automerge(pr_labels):
140+
for label in pr_labels:
141+
if label["name"] == AUTOMERGE_LABEL:
142+
return True
143+
return False
144+
145+
146+
async def get_pr_for_commit(gh, sha):
147+
prs_for_commit = await gh.getitem(
148+
f"/search/issues?q=type:pr+repo:python/cpython+sha:{sha}"
149+
)
150+
if prs_for_commit["total_count"] > 0: # there should only be one
151+
pr_for_commit = prs_for_commit["items"][0]
152+
return pr_for_commit
153+
return None

0 commit comments

Comments
 (0)