mirror of
https://github.com/acidanthera/audk.git
synced 2025-04-08 17:05:09 +02:00
.github: Add GitHub helper python script
Adds a script that provides GitHub API helpers for workflows and other GitHub automation in the repository. Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
This commit is contained in:
parent
3f0c4cee94
commit
89a06a245b
187
.github/scripts/GitHub.py
vendored
Normal file
187
.github/scripts/GitHub.py
vendored
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
## @file
|
||||||
|
# GitHub API helper functions.
|
||||||
|
#
|
||||||
|
# Copyright (c) Microsoft Corporation.
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||||
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
from edk2toollib.utility_functions import RunCmd, RunPythonScript
|
||||||
|
from io import StringIO
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
"""GitHub API helper functions."""
|
||||||
|
|
||||||
|
|
||||||
|
def leave_pr_comment(
|
||||||
|
token: str, owner: str, repo: str, pr_number: str, comment_body: str
|
||||||
|
):
|
||||||
|
"""Leaves a comment on a PR.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
token (str): The GitHub token to use for authentication.
|
||||||
|
owner (str): The GitHub owner (organization) name.
|
||||||
|
repo (str): The GitHub repository name (e.g. 'edk2').
|
||||||
|
pr_number (str): The pull request number.
|
||||||
|
comment_body (str): The comment text. Markdown is supported.
|
||||||
|
"""
|
||||||
|
url = f"https://api.github.com/repos/{owner}/{repo}/issues/{pr_number}/comments"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
}
|
||||||
|
data = {"body": comment_body}
|
||||||
|
response = requests.post(url, json=data, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
|
||||||
|
def get_reviewers_for_current_branch(
|
||||||
|
workspace_path: str, maintainer_file_path: str, target_branch: str = "master"
|
||||||
|
) -> List[str]:
|
||||||
|
"""Get the reviewers for the current branch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
workspace_path (str): The workspace path.
|
||||||
|
maintainer_file_path (str): The maintainer file path.
|
||||||
|
target_branch (str, optional): The name of the target branch that the
|
||||||
|
current HEAD will merge to. Defaults to "master".
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[str]: A list of GitHub usernames.
|
||||||
|
"""
|
||||||
|
|
||||||
|
commit_stream_buffer = StringIO()
|
||||||
|
cmd_ret = RunCmd(
|
||||||
|
"git",
|
||||||
|
f"log --format=format:%H {target_branch}..HEAD",
|
||||||
|
workingdir=workspace_path,
|
||||||
|
outstream=commit_stream_buffer,
|
||||||
|
logging_level=logging.INFO,
|
||||||
|
)
|
||||||
|
if cmd_ret != 0:
|
||||||
|
print(
|
||||||
|
f"::error title=Commit Lookup Error!::Error getting branch commits: [{cmd_ret}]: {commit_stream_buffer.getvalue()}"
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
raw_reviewers = []
|
||||||
|
for commit_sha in commit_stream_buffer.getvalue().splitlines():
|
||||||
|
reviewer_stream_buffer = StringIO()
|
||||||
|
cmd_ret = RunPythonScript(
|
||||||
|
maintainer_file_path,
|
||||||
|
f"-g {commit_sha}",
|
||||||
|
workingdir=workspace_path,
|
||||||
|
outstream=reviewer_stream_buffer,
|
||||||
|
logging_level=logging.INFO,
|
||||||
|
)
|
||||||
|
if cmd_ret != 0:
|
||||||
|
print(
|
||||||
|
f"::error title=Reviewer Lookup Error!::Error calling GetMaintainer.py: [{cmd_ret}]: {reviewer_stream_buffer.getvalue()}"
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
commit_reviewers = reviewer_stream_buffer.getvalue()
|
||||||
|
|
||||||
|
pattern = r"\[(.*?)\]"
|
||||||
|
matches = re.findall(pattern, commit_reviewers)
|
||||||
|
if not matches:
|
||||||
|
return []
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"::debug title=Commit {commit_sha[:7]} Reviewer(s)::{', '.join(matches)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
raw_reviewers.extend(matches)
|
||||||
|
|
||||||
|
reviewers = list(OrderedDict.fromkeys([r.strip() for r in raw_reviewers]))
|
||||||
|
|
||||||
|
print(f"::debug title=Total Reviewer Set::{', '.join(reviewers)}")
|
||||||
|
|
||||||
|
return reviewers
|
||||||
|
|
||||||
|
|
||||||
|
def download_gh_file(github_url: str, local_path: str, token=None):
|
||||||
|
"""Downloads a file from GitHub.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
github_url (str): The GitHub raw file URL.
|
||||||
|
local_path (str): A local path to write the file contents to.
|
||||||
|
token (_type_, optional): A GitHub authentication token.
|
||||||
|
Only needed for a private repo. Defaults to None.
|
||||||
|
"""
|
||||||
|
headers = {}
|
||||||
|
if token:
|
||||||
|
headers["Authorization"] = f"Bearer {token}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(github_url, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.exceptions.HTTPError:
|
||||||
|
print(
|
||||||
|
f"::error title=HTTP Error!::Error downloading {github_url}: {response.reason}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(local_path, "w", encoding="utf-8") as file:
|
||||||
|
file.write(response.text)
|
||||||
|
|
||||||
|
|
||||||
|
def add_reviewers_to_pr(
|
||||||
|
token: str, owner: str, repo: str, pr_number: str, user_names: List[str]
|
||||||
|
):
|
||||||
|
"""Adds the set of GitHub usernames as reviewers to the PR.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
token (str): The GitHub token to use for authentication.
|
||||||
|
owner (str): The GitHub owner (organization) name.
|
||||||
|
repo (str): The GitHub repository name (e.g. 'edk2').
|
||||||
|
pr_number (str): The pull request number.
|
||||||
|
user_names (List[str]): List of GitHub usernames to add as reviewers.
|
||||||
|
"""
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
}
|
||||||
|
pr_author_url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
|
||||||
|
url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}/requested_reviewers"
|
||||||
|
|
||||||
|
response = requests.get(pr_author_url, headers=headers)
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f"::error title=HTTP Error!::Error getting PR author: {response.reason}")
|
||||||
|
return
|
||||||
|
pr_author = response.json().get("user").get("login").strip()
|
||||||
|
while pr_author in user_names:
|
||||||
|
user_names.remove(pr_author)
|
||||||
|
data = {"reviewers": user_names}
|
||||||
|
response = requests.post(url, json=data, headers=headers)
|
||||||
|
try:
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.exceptions.HTTPError:
|
||||||
|
if (
|
||||||
|
response.status_code == 422
|
||||||
|
and "Reviews may only be requested from collaborators"
|
||||||
|
in response.json().get("message")
|
||||||
|
):
|
||||||
|
print(
|
||||||
|
f"::error title=User is not a Collaborator!::{response.json().get('message')}"
|
||||||
|
)
|
||||||
|
leave_pr_comment(
|
||||||
|
token,
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pr_number,
|
||||||
|
f"⚠ **WARNING: Cannot add reviewers**: A user specified as a "
|
||||||
|
f"reviewer for this PR is not a collaborator "
|
||||||
|
f"of the edk2 repository. Please add them as a collaborator to the "
|
||||||
|
f"repository and re-request the review.\n\n"
|
||||||
|
f"Users requested:\n{', '.join(user_names)}",
|
||||||
|
)
|
||||||
|
elif response.status_code == 422:
|
||||||
|
print(
|
||||||
|
"::error title=Invalid Request!::The request is invalid. "
|
||||||
|
"Verify the API request string."
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user