Skip to content

Commit c30c646

Browse files
Update github_rest_api/github.py
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent c8f734b commit c30c646

1 file changed

Lines changed: 37 additions & 8 deletions

File tree

github_rest_api/github.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from datetime import datetime, timedelta, timezone
1010
from enum import StrEnum
1111
from pathlib import Path
12-
from typing import Any, Callable
12+
from typing import Any, Callable, TypeGuard
1313
from urllib.parse import quote
1414

1515
import requests
@@ -108,6 +108,32 @@ def _login(obj: dict[str, Any]) -> str | None:
108108
return (obj.get("user") or {}).get("login")
109109

110110

111+
def _normalized_logins(logins: Sequence[str]) -> set[str]:
112+
"""Lower-case GitHub logins into a set for case-insensitive membership tests.
113+
114+
GitHub logins are case-insensitive, so normalizing the allowlist once (rather
115+
than per candidate) lets repeated membership tests compare against a stable
116+
lower-cased set.
117+
118+
:param logins: The logins eligible for a gated action.
119+
"""
120+
return {login.lower() for login in logins}
121+
122+
123+
def _in_allowlist(login: str | None, allowlist: set[str]) -> TypeGuard[str]:
124+
"""Check whether a GitHub login is in a normalized allowlist.
125+
126+
``allowlist`` must already be lower-cased (see :func:`_normalized_logins`).
127+
The login is lower-cased here before comparison, so casing differences
128+
between the configured allowlist and the API response do not cause spurious
129+
mismatches. A ``None`` login (e.g. a ghost/deleted account) is never a member.
130+
131+
:param login: The login to test, or None.
132+
:param allowlist: A lower-cased set of logins eligible for the gated action.
133+
"""
134+
return login is not None and login.lower() in allowlist
135+
136+
111137
# Matches the leading Conventional-Commits type token of a title, e.g. the
112138
# `chore` in "chore(deps): bump x" or "feat!: ...".
113139
_CONVENTIONAL_TYPE = re.compile(r"^(\w+)(\([^)]*\))?!?:")
@@ -149,7 +175,7 @@ def _field_gate_failure(
149175
"""
150176
if pr.get("draft"):
151177
return "it is a draft"
152-
if _login(pr) not in set(authors):
178+
if not _in_allowlist(_login(pr), _normalized_logins(authors)):
153179
return "its author is not in the allowlist"
154180
if not _title_type_allowed(pr.get("title") or "", allowed_types):
155181
return "its title type is not auto-merge eligible"
@@ -174,14 +200,14 @@ def _approver_review_states(
174200
:param reviews: Reviews as returned by ``get_pull_request_reviews``.
175201
:param approvers: Logins whose reviews are trusted for auto-merge.
176202
"""
177-
approver_set = set(approvers)
203+
approver_set = _normalized_logins(approvers)
178204
# Reviews are returned in chronological order, so a later decisive entry
179205
# overrides an earlier one for the same reviewer.
180206
latest: dict[str, str] = {}
181207
for review in reviews:
182208
login = _login(review)
183209
state = review.get("state")
184-
if login in approver_set and state in (
210+
if _in_allowlist(login, approver_set) and state in (
185211
"APPROVED",
186212
"CHANGES_REQUESTED",
187213
"DISMISSED",
@@ -199,9 +225,10 @@ def _marker_approved(
199225
:param approvers: Logins whose marker comments are trusted for auto-merge.
200226
:param marker: The marker substring signalling approval.
201227
"""
202-
approver_set = set(approvers)
228+
approver_set = _normalized_logins(approvers)
203229
return any(
204-
_login(comment) in approver_set and marker in (comment.get("body") or "")
230+
_in_allowlist(_login(comment), approver_set)
231+
and marker in (comment.get("body") or "")
205232
for comment in comments
206233
)
207234

@@ -731,9 +758,11 @@ def auto_merge_pull_requests(
731758
# Pin the merge to the SHA the gate validated so a push that
732759
# landed in between is rejected (409) instead of merged.
733760
self.merge_pull_request(number, merge_method, sha=sha)
734-
except requests.HTTPError:
761+
except Exception:
735762
logger.warning(
736-
"Skipping PR #%s after a request failure.", number, exc_info=True
763+
"Skipping PR #%s after an unexpected failure.",
764+
number,
765+
exc_info=True,
737766
)
738767
continue
739768
eligible.append(number)

0 commit comments

Comments
 (0)