# (c) 2020 Humu # MIT License from typing import Any import github AUTOMERGE_LABEL_NAME = 'automerge' class Automerge: def __init__(self, githubClient: github.Github) -> None: self._github = githubClient def _hasAutomergeLabel(self, pr: github.PullRequest.PullRequest) -> bool: return any(label.name == AUTOMERGE_LABEL_NAME for label in pr.labels) def processWebhookEvent(self, eventType: str, eventJson: Any) -> None: # If a PR becomes green, merge it. # If new commits are pushed, a PR check fails, or the PR is closed, remove the label. if eventType == 'status': # https://developer.github.com/v3/activity/events/types/#statusevent repo = self._github.create_from_raw_data( github.Repository.Repository, eventJson['repository'] ) commit = self._github.create_from_raw_data(github.Commit.Commit, eventJson['commit']) # Shockingly, GitHub gives no way to get the PR associated with a 'status' event. You # need to search the list of PRs. matchingPr = next( (pr for pr in repo.get_pulls(sort='updated') if pr.head.sha == commit.sha), None ) if matchingPr and self._hasAutomergeLabel(matchingPr): # Clean means green, unstable means yellow, which happens often with Reviewable, # for whatever reason. (If the PR is not accepted or one of our required checks is # failing, it'll be 'blocked'.) # https://developer.github.com/v4/enum/mergestatestatus/ if matchingPr.mergeable_state in ('clean', 'unstable'): matchingPr.merge(merge_method='squash') # If a check fails, we leave the label so that if it's a flaky test it can be rerun. # (If a test fails due to a code issue and new commits are pushed as a fix, the # label will be removed due to the 'synchronize' below.) elif eventType == 'pull_request': # https://developer.github.com/v3/activity/events/types/#pullrequestevent pr = self._github.create_from_raw_data( github.PullRequest.PullRequest, eventJson['pull_request'] ) if self._hasAutomergeLabel(pr): # 'synchonize' means new commits were pushed if eventJson['action'] == 'synchronize' or ( eventJson['action'] == 'closed' and not pr.merged ): pr.remove_from_labels(AUTOMERGE_LABEL_NAME) elif eventType == 'pull_request_review': # https://developer.github.com/v3/activity/events/types/#pullrequestreviewevent pr = self._github.create_from_raw_data( github.PullRequest.PullRequest, eventJson['pull_request'] ) review = self._github.create_from_raw_data( github.PullRequestReview.PullRequestReview, eventJson['review'] ) # Some API docs have 'state' values capitalized, some lowercase. Why? Don't know. if eventJson['action'] == 'submitted' and review.state.lower() == 'changes_requested': if self._hasAutomergeLabel(pr): pr.remove_from_labels(AUTOMERGE_LABEL_NAME)