Skip to content

Commit

Permalink
Automerge PR labeled with "automerge". (#146)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Mariatta authored Sep 11, 2018
1 parent 85c6a4e commit 3215042
Show file tree
Hide file tree
Showing 5 changed files with 499 additions and 53 deletions.
2 changes: 1 addition & 1 deletion miss_islington/backport_pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async def backport_pr(event, gh, *args, **kwargs):
if random.random() < 0.1:
easter_egg = EASTER_EGG
thanks_to = ""
if created_by == merged_by:
if created_by == merged_by or merged_by == "miss-islington":
thanks_to = f"Thanks @{created_by} for the PR 🌮🎉."
else:
thanks_to = f"Thanks @{created_by} for the PR, and @{merged_by} for merging it 🌮🎉."
Expand Down
109 changes: 70 additions & 39 deletions miss_islington/status_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,44 @@ async def check_status(event, gh, *args, **kwargs):
"""
Check the state change
"""
sha = event.data["sha"]

if (
event.data["commit"].get("committer")
and event.data["commit"]["committer"]["login"] == "miss-islington"
):
sha = event.data["sha"]
await check_ci_status_and_approval(gh, sha, leave_comment=True)
else:
pr_for_commit = await util.get_pr_for_commit(gh, sha)
if pr_for_commit:
pr_labels = pr_for_commit["labels"]
if util.pr_is_automerge(pr_labels) and util.pr_is_awaiting_merge(pr_labels):
await check_ci_status_and_approval(
gh, sha, leave_comment=True, is_automerge=True
)


@router.register("pull_request", action="labeled")
async def pr_reviewed(event, gh, *args, **kwargs):
if event.data["pull_request"]["user"]["login"] == "miss-islington":
if util.pr_is_awaiting_merge(event.data["pull_request"]["labels"]):
sha = event.data["pull_request"]["head"]["sha"]
await check_ci_status_and_approval(gh, sha)

pr_labels = event.data["pull_request"]["labels"]

if util.pr_is_automerge(pr_labels) and util.pr_is_awaiting_merge(pr_labels):
sha = event.data["pull_request"]["head"]["sha"]

await check_ci_status_and_approval(gh, sha, is_automerge=True)
elif event.data["pull_request"]["user"][
"login"
] == "miss-islington" and util.pr_is_awaiting_merge(
event.data["pull_request"]["labels"]
):
sha = event.data["pull_request"]["head"]["sha"]
await check_ci_status_and_approval(gh, sha)


async def check_ci_status_and_approval(gh, sha, leave_comment=False):
async def check_ci_status_and_approval(
gh, sha, leave_comment=False, is_automerge=False
):

result = await gh.getitem(f"/repos/python/cpython/commits/{sha}/status")
all_ci_status = [status["state"] for status in result["statuses"]]
Expand All @@ -40,56 +61,66 @@ async def check_ci_status_and_approval(gh, sha, leave_comment=False):
"pending" not in all_ci_status
and "continuous-integration/travis-ci/pr" in all_ci_context
):

prs_for_commit = await gh.getitem(f'/search/issues?q=type:pr+repo:python/cpython+sha:{sha}')
if prs_for_commit["total_count"] > 0: # there should only be one
pr_for_commit = prs_for_commit["items"][0]
pr_for_commit = await util.get_pr_for_commit(gh, sha)
if pr_for_commit:
pr_number = pr_for_commit["number"]
normalized_pr_title = util.normalize_title(
pr_for_commit["title"], pr_for_commit["body"]
)

title_match = TITLE_RE.match(normalized_pr_title)
if title_match:
if title_match or is_automerge:
if leave_comment:
original_pr_number = title_match.group("pr")
original_pr_url = (
f"/repos/python/cpython/pulls/{original_pr_number}"
)
original_pr_result = await gh.getitem(original_pr_url)
pr_author = original_pr_result["user"]["login"]
committer = original_pr_result["merged_by"]["login"]
if is_automerge:
participants = await util.get_gh_participants(gh, pr_number)
else:
original_pr_number = title_match.group("pr")
participants = await util.get_gh_participants(
gh, original_pr_number
)

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

await util.leave_comment(
gh,
pr_number=pr_number,
message=f"{participants}: Backport status check is done, and it's a {result['state']} {emoji} .",
message=f"{participants}: Status check is done, and it's a {result['state']} {emoji} .",
)

if result["state"] == "success":
pr = await gh.getitem(
f"/repos/python/cpython/pulls/{pr_number}"
)
if util.pr_is_awaiting_merge(pr["labels"]):
await merge_pr(gh, pr_number, sha)

if util.pr_is_awaiting_merge(pr_for_commit["labels"]):
await merge_pr(
gh, pr_for_commit, sha, is_automerge=is_automerge
)

async def merge_pr(gh, pr_number, sha):

async def merge_pr(gh, pr, sha, is_automerge=False):
pr_number = pr["number"]
async for commit in gh.getiter(f"/repos/python/cpython/pulls/{pr_number}/commits"):
if commit["sha"] == sha:
pr_commit_msg = commit["commit"]["message"].split("\n")

cleaned_up_title = f"{pr_commit_msg[0]}"
await gh.put(
f"/repos/python/cpython/pulls/{pr_number}/merge",
data={
"commit_title": cleaned_up_title,
"commit_message": "\n".join(pr_commit_msg[1:]),
"sha": sha,
"merge_method": "squash",
},
)
if is_automerge:
pr_commit_msg = pr["body"]
pr_title = f"{pr['title']} (GH-{pr_number})"
await gh.put(
f"/repos/python/cpython/pulls/{pr_number}/merge",
data={
"commit_title": pr_title,
"commit_message": pr_commit_msg,
"sha": sha,
"merge_method": "squash",
},
)
else:
pr_commit_msg = commit["commit"]["message"].split("\n")

cleaned_up_title = f"{pr_commit_msg[0]}"
await gh.put(
f"/repos/python/cpython/pulls/{pr_number}/merge",
data={
"commit_title": cleaned_up_title,
"commit_message": "\n".join(pr_commit_msg[1:]),
"sha": sha,
"merge_method": "squash",
},
)
break
40 changes: 39 additions & 1 deletion miss_islington/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from gidgethub import sansio


AUTOMERGE_LABEL = ":robot: automerge"


def comment_on_pr(issue_number, message):
"""
Leave a comment on a PR/Issue
Expand Down Expand Up @@ -66,9 +69,27 @@ def is_cpython_repo():
return True


async def get_gh_participants(gh, pr_number):
pr_url = f"/repos/python/cpython/pulls/{pr_number}"
pr_result = await gh.getitem(pr_url)
created_by = pr_result["user"]["login"]

merged_by = None
if pr_result["merged_by"] and pr_result["merged_by"]["login"] != "miss-islington":
merged_by = pr_result["merged_by"]["login"]

participants = ""
if created_by == merged_by or merged_by is None:
participants = f"@{created_by}"
else:
participants = f"@{created_by} and @{merged_by}"

return participants


def get_participants(created_by, merged_by):
participants = ""
if created_by == merged_by:
if created_by == merged_by or merged_by == "miss-islington":
participants = f"@{created_by}"
else:
participants = f"@{created_by} and @{merged_by}"
Expand Down Expand Up @@ -113,3 +134,20 @@ def pr_is_awaiting_merge(pr_labels):
if label["name"] == "awaiting merge":
return True
return False


def pr_is_automerge(pr_labels):
for label in pr_labels:
if label["name"] == AUTOMERGE_LABEL:
return True
return False


async def get_pr_for_commit(gh, sha):
prs_for_commit = await gh.getitem(
f"/search/issues?q=type:pr+repo:python/cpython+sha:{sha}"
)
if prs_for_commit["total_count"] > 0: # there should only be one
pr_for_commit = prs_for_commit["items"][0]
return pr_for_commit
return None
Loading

0 comments on commit 3215042

Please sign in to comment.