diff --git a/action.yaml b/action.yaml index 36344fd..a425fd8 100644 --- a/action.yaml +++ b/action.yaml @@ -51,11 +51,12 @@ runs: # get list of reviewers for PR - run: | - git fetch \ - && echo "REVIEWERS=$( \ + echo "REVIEWERS=$( \ + GITHUB_TOKEN=${{ github.token }} \ python $GITHUB_ACTION_PATH/deploy_bot/get_reviewers.py \ - --base-branch=origin/${{ env.DEPLOYMENT_BRANCH }} \ - --target-branch=origin/${{ inputs.target }} \ + --repository ${{ github.repository }} \ + --head-branch=${{ env.DEPLOYMENT_BRANCH }} \ + --base-branch=${{ inputs.target }} \ )" >> $GITHUB_ENV shell: bash diff --git a/deploy_bot/get_reviewers.py b/deploy_bot/get_reviewers.py index a5ed61e..5b28358 100644 --- a/deploy_bot/get_reviewers.py +++ b/deploy_bot/get_reviewers.py @@ -1,116 +1,33 @@ -import re -import subprocess -from typing import Any, Dict, List -import warnings +import os +from typing import Dict, List, Set import requests +GITHUB_TOKEN: str = os.getenv("GITHUB_TOKEN") -def find_contributors_to_branch(base_branch: str, target_branch: str) -> List[str]: - """Finds authors of commits to base branch that are NOT in target branch. - :param base_branch: base branch of diff - :param target_branch: target branch of diff - :return: list of authors of commits in base branch but not in target - """ - commit_hashes: List[str] = find_added_commits(base_branch=base_branch, target_branch=target_branch) - - return [find_author_of_commit(commit_hash) for commit_hash in commit_hashes] - - -def find_author_of_commit(commit_hash: str) -> str: - """Finds GitHub username of author of commit. - - :param commit_hash: commit hash to find author for - :return: GitHub username of author of commit hash - """ - # run `git log` to find author emails - command_output: subprocess.CompletedProcess = subprocess.run( - ["git", "log", commit_hash, "-1", "--format='%ae'"], - capture_output=True, - check=True, - ) - - # parse author email from command output - author_email: List[str] = command_output.stdout.decode().splitlines()[0][1:-1] - - # get and return GitHub usernames from emails - return get_github_username_from_email(author_email) or "" - - -def get_github_username_from_email(author_email: str) -> str: - """Gets GitHub username from email, using GitHub API. - - Note: - This raises a warning if a username cannot be found, or if multiple usernames are found. - - :param author_email: email of author to query username for - :return: GitHub username corresponding to email (or None if more or less thann one username is found) - """ - gh_api_response: requests.Response = requests.get( - "https://api.github.com/search/users", params=dict(q=author_email), - ) - - assert gh_api_response.status_code == 200, "Bad response from GitHub users search API." - - response_dict: Dict[str, Any] = gh_api_response.json() - - # check that one username was returned - num_usernames: int = response_dict.get("total_count", 0) - if num_usernames == 0: - warnings.warn(f"No username found for email: {author_email} -- cannot tag reviewer.") - return None - - elif num_usernames > 1: - warnings.warn(f"Multiple usernames found for email: {author_email} -- cannot tag reviewer.") - return None - - # get and return username for email - return response_dict.get("items")[0]["login"] - - -def find_added_commits(base_branch: str, target_branch: str) -> List[str]: - """Finds commits to base branch that are NOT in target branch. +def find_contributors_to_branch(repository: str, head_branch: str, base_branch: str) -> Set[str]: + """Finds authors of commits to head branch that are NOT in base branch. + :param head_branch: head branch of diff :param base_branch: base branch of diff - :param target_branch: target branch of diff - :return: commit hashes in base branch that are NOT in target - """ - # run `git cherry` to find commits - command_output: subprocess.CompletedProcess = subprocess.run( - ["git", "cherry", target_branch, base_branch], - capture_output=True, - check=True, - ) - - # parse and return commit hashes from command output - return [ - get_commit_hash_from_git_cherry_line(line) - for line in command_output.stdout.decode().splitlines() - if is_added_commit(line) - ] - - -def get_commit_hash_from_git_cherry_line(line: str) -> str: - """Parses git cherry line into commit hash. - - :param line: line from git cherry output to parse - :return: commit hash + :return: set of authors of commits in head branch but not in base """ - try: - return re.search(r"\b([0-9a-f]{5,40})\b", line).group(1) - except AttributeError: - raise RuntimeError("Could not parse commit hash from git cherry -- aborting.") + gh_api_response: requests.Response = requests.get( + f"https://api.github.com/repos/{repository}/compare/{base_branch}...{head_branch}", + headers={ + "Authorization": f"Bearer {GITHUB_TOKEN}", + "Accept": "application/vnd.github+json", + } +) + assert gh_api_response.status_code == 200, f"Bad response from GitHub compare API {gh_api_response.url, gh_api_response.text}." -def is_added_commit(line: str) -> bool: - """Returns true if line is of an added commit (starts with '+'). + response_dict: Dict = gh_api_response.json() + commits: List[Dict] = response_dict.get("commits") - :param line: line to determine if is an addition - :return: true if line is an addition - """ - return line.startswith("+") + return {commit.get("author").get("login") for commit in commits} if __name__ == "__main__": @@ -118,21 +35,25 @@ def is_added_commit(line: str) -> bool: import argparse parser: argparse.ArgumentParser = argparse.ArgumentParser(prog="Get reviewers for PR") + parser.add_argument( + "--repository", + help="repository to get diff from", + ) + parser.add_argument( + "--head-branch", + dest="head_branch", + help="head branch for comparison", + ) parser.add_argument( "--base-branch", dest="base_branch", help="base branch for comparison", ) - parser.add_argument( - "--target-branch", - dest="target_branch", - help="target branch for comparison", - ) args = parser.parse_args() # find contributors to branch - contributors: List[str] = find_contributors_to_branch(args.base_branch, args.target_branch) + contributors: Set[str] = find_contributors_to_branch(args.repository, args.head_branch, args.base_branch) # output contributors as comma delimited list print(",".join(contributors))