Skip to content

Commit

Permalink
Merge branch 'master' into acpiview-spmi
Browse files Browse the repository at this point in the history
  • Loading branch information
shkrc authored Sep 17, 2024
2 parents 620c27a + 670e263 commit 7ec25de
Show file tree
Hide file tree
Showing 809 changed files with 58,092 additions and 13,011 deletions.
284 changes: 167 additions & 117 deletions .github/scripts/GitHub.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,80 @@
# SPDX-License-Identifier: BSD-2-Clause-Patent
#

import git
import logging
import re
import requests

from collections import OrderedDict
from edk2toollib.utility_functions import RunCmd, RunPythonScript
from edk2toollib.utility_functions import RunPythonScript
from github import Auth, Github, GithubException
from io import StringIO
from typing import List


"""GitHub API helper functions."""


def _authenticate(token: str):
"""Authenticate to GitHub using a token.
Returns a GitHub instance that is authenticated using the provided
token.
Args:
token (str): The GitHub token to use for authentication.
Returns:
Github: A GitHub instance.
"""
auth = Auth.Token(token)
return Github(auth=auth)


def _get_pr(token: str, owner: str, repo: str, pr_number: int):
"""Get the PR object from GitHub.
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 (int): The pull request number.
Returns:
PullRequest: A PyGithub PullRequest object for the given pull request
or None if the attempt to get the PR fails.
"""
try:
g = _authenticate(token)
return g.get_repo(f"{owner}/{repo}").get_pull(pr_number)
except GithubException as ge:
print(
f"::error title=Error Getting PR {pr_number} Info!::"
f"{ge.data['message']}"
)
return None


def leave_pr_comment(
token: str, owner: str, repo: str, pr_number: str, comment_body: str
token: str, owner: str, repo: str, pr_number: int, 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.
pr_number (int): 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()
if pr := _get_pr(token, owner, repo, pr_number):
try:
pr.create_issue_comment(comment_body)
except GithubException as ge:
print(
f"::error title=Error Commenting on PR {pr_number}!::"
f"{ge.data['message']}"
)


def get_reviewers_for_range(
Expand All @@ -47,8 +89,11 @@ def get_reviewers_for_range(
) -> List[str]:
"""Get the reviewers for the current branch.
To get the reviewers for a single commit, set `range_start` and
`range_end` to the commit SHA.
!!! note
This function accepts a range of commits and returns the reviewers
for that set of commits as a single list of GitHub usernames. To get
the reviewers for a single commit, set `range_start` and `range_end`
to the commit SHA.
Args:
workspace_path (str): The workspace path.
Expand All @@ -59,24 +104,15 @@ def get_reviewers_for_range(
Returns:
List[str]: A list of GitHub usernames.
"""

if range_start == range_end:
commits = [range_start]
else:
commit_stream_buffer = StringIO()
cmd_ret = RunCmd(
"git",
f"log --format=format:%H {range_start}..{range_end}",
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()}"
commits = [
c.hexsha
for c in git.Repo(workspace_path).iter_commits(
f"{range_start}..{range_end}"
)
return []
commits = commit_stream_buffer.getvalue().splitlines()
]

raw_reviewers = []
for commit_sha in commits:
Expand All @@ -90,7 +126,9 @@ def get_reviewers_for_range(
)
if cmd_ret != 0:
print(
f"::error title=Reviewer Lookup Error!::Error calling GetMaintainer.py: [{cmd_ret}]: {reviewer_stream_buffer.getvalue()}"
f"::error title=Reviewer Lookup Error!::Error calling "
f"GetMaintainer.py: [{cmd_ret}]: "
f"{reviewer_stream_buffer.getvalue()}"
)
return []

Expand All @@ -102,7 +140,8 @@ def get_reviewers_for_range(
return []

print(
f"::debug title=Commit {commit_sha[:7]} Reviewer(s)::{', '.join(matches)}"
f"::debug title=Commit {commit_sha[:7]} "
f"Reviewer(s)::{', '.join(matches)}"
)

raw_reviewers.extend(matches)
Expand All @@ -114,122 +153,133 @@ def get_reviewers_for_range(
return reviewers


def get_pr_sha(token: str, owner: str, repo: str, pr_number: str) -> str:
def get_pr_sha(token: str, owner: str, repo: str, pr_number: int) -> str:
"""Returns the commit SHA of given PR branch.
This returns the SHA of the merge commit that GitHub creates from a
PR branch. This commit contains all of the files in the PR branch in
a single commit.
This returns the SHA of the merge commit that GitHub creates from a
PR branch. This commit contains all of the files in the PR branch in
a single commit.
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.
pr_number (int): The pull request number.
Returns:
str: The commit SHA of the PR branch. An empty string is returned
if the request fails.
"""
url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(url, headers=headers)
try:
response.raise_for_status()
except requests.exceptions.HTTPError:
print(
f"::error title=HTTP Error!::Error getting PR Commit Info: {response.reason}"
)
return ""

commit_sha = response.json()["merge_commit_sha"]
if pr := _get_pr(token, owner, repo, pr_number):
merge_commit_sha = pr.merge_commit_sha
print(f"::debug title=PR {pr_number} Merge Commit SHA::{merge_commit_sha}")
return merge_commit_sha

print(f"::debug title=PR {pr_number} Commit SHA::{commit_sha}")
return ""

return commit_sha


def download_gh_file(github_url: str, local_path: str, token=None):
"""Downloads a file from GitHub.
def add_reviewers_to_pr(
token: str, owner: str, repo: str, pr_number: int, user_names: List[str]
) -> List[str]:
"""Adds the set of GitHub usernames as reviewers to the PR.
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.
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 (int): The pull request number.
user_names (List[str]): List of GitHub usernames to add as reviewers.
Returns:
List[str]: A list of GitHub usernames that were successfully added as
reviewers to the PR. This list will exclude any reviewers
from the list provided if they are not relevant to the PR.
"""
headers = {}
if token:
headers["Authorization"] = f"Bearer {token}"
if not user_names:
print(
"::debug title=No PR Reviewers Requested!::"
"The list of PR reviewers is empty so not adding any reviewers."
)
return []

try:
response = requests.get(github_url, headers=headers)
response.raise_for_status()
except requests.exceptions.HTTPError:
g = _authenticate(token)
repo_gh = g.get_repo(f"{owner}/{repo}")
pr = repo_gh.get_pull(pr_number)
except GithubException as ge:
print(
f"::error title=HTTP Error!::Error downloading {github_url}: {response.reason}"
f"::error title=Error Getting PR {pr_number} Info!::"
f"{ge.data['message']}"
)
return
return None

with open(local_path, "w", encoding="utf-8") as file:
file.write(response.text)
# The pull request author cannot be a reviewer.
pr_author = pr.user.login.strip()

# The current PR reviewers do not need to be requested again.
current_pr_requested_reviewers = [
r.login.strip() for r in pr.get_review_requests()[0]
]
current_pr_reviewed_reviewers = [r.user.login.strip() for r in pr.get_reviews()]
current_pr_reviewers = list(
set(current_pr_requested_reviewers + current_pr_reviewed_reviewers)
)

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.
# A user can only be added if they are a collaborator of the repository.
repo_collaborators = [c.login.strip() for c in repo_gh.get_collaborators()]
non_collaborators = [u for u in user_names if u not in repo_collaborators]

excluded_pr_reviewers = [pr_author] + current_pr_reviewers + non_collaborators
new_pr_reviewers = [u for u in user_names if u not in excluded_pr_reviewers]

# Notify the admins of the repository if non-collaborators are requested.
if non_collaborators:
print(
f"::warning title=Non-Collaborator Reviewers Found!::"
f"{', '.join(non_collaborators)}"
)

for comment in pr.get_issue_comments():
# If a comment has already been made for these non-collaborators,
# do not make another comment.
if (
comment.user.login == "github-actions[bot]"
and "WARNING: Cannot add some reviewers" in comment.body
and all(u in comment.body for u in non_collaborators)
):
break
else:
repo_admins = [
a.login for a in repo_gh.get_collaborators(permission="admin")
]

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."
f"⚠ **WARNING: Cannot add some reviewers**: A user "
f"specified as a reviewer for this PR is not a collaborator "
f"of the repository. Please add them as a collaborator to "
f"the repository so they can be requested in the future.\n\n"
f"Non-collaborators requested:\n"
f"{'\n'.join([f'- @{c}' for c in non_collaborators])}"
f"\n\nAttn Admins:\n"
f"{'\n'.join([f'- @{a}' for a in repo_admins])}\n---\n"
f"**Admin Instructions:**\n"
f"- Add the non-collaborators as collaborators to the "
f"appropriate team(s) listed in "
f"[teams](https://github.com/orgs/tianocore/teams)\n"
f"- If they are no longer needed as reviewers, remove them "
f"from [`Maintainers.txt`](https://github.com/tianocore/edk2/blob/HEAD/Maintainers.txt)",
)

# Add any new reviewers to the PR if needed.
if new_pr_reviewers:
print(
f"::debug title=Adding New PR Reviewers::" f"{', '.join(new_pr_reviewers)}"
)

pr.create_review_request(reviewers=new_pr_reviewers)

return new_pr_reviewers
Loading

0 comments on commit 7ec25de

Please sign in to comment.