|
21 | 21 | - If a PR is not linked and no prior reminder is present, the script posts a single |
22 | 22 | friendly reminder comment. |
23 | 23 | - PRs labeled `no-issue-needed` and bot-authored PRs are skipped. |
| 24 | +- PRs authored by maintainers, users with write (or admin) access, and collaborators |
| 25 | + are skipped; the reminder only targets external contributors. |
24 | 26 | """ |
25 | 27 |
|
26 | 28 | import logging |
27 | 29 | import os |
| 30 | +import re |
28 | 31 | from datetime import datetime, timedelta, timezone |
29 | 32 |
|
30 | 33 | import requests |
|
37 | 40 | REMINDER_MARKER = "<!-- pr-link-issue-reminder -->" |
38 | 41 | BYPASS_LABELS = {"no-issue-needed"} |
39 | 42 | LOOKBACK_DAYS = 2 |
| 43 | +# Collaborator permission levels that mark a PR author as a maintainer / writer / |
| 44 | +# collaborator. Authors with any of these are skipped (the reminder is only for |
| 45 | +# external contributors). |
| 46 | +PRIVILEGED_PERMISSIONS = {"admin", "write", "maintain", "triage"} |
| 47 | + |
| 48 | +# `author_association` values that mark the author as a maintainer / collaborator. |
| 49 | +# These are available on the PR payload without needing extra token scopes. |
| 50 | +PRIVILEGED_ASSOCIATIONS = {"OWNER", "MEMBER", "COLLABORATOR"} |
| 51 | + |
| 52 | +# A PR authored by the model/pipeline's own team does not need to link an issue. |
| 53 | +# Matches a checked task-list item for the corresponding PR template checkbox. |
| 54 | +AUTHOR_CHECKBOX_PATTERN = re.compile( |
| 55 | + r"-\s*\[\s*[xX]\s*\]\s*Are you the author \(or part of the team\) of the model/pipeline" |
| 56 | +) |
40 | 57 | CONTRIBUTION_GUIDE_URL = "https://huggingface.co/docs/diffusers/main/en/conceptual/contribution#coding-with-ai-agents" |
41 | 58 |
|
42 | 59 | GRAPHQL_URL = "https://api.github.com/graphql" |
@@ -68,10 +85,31 @@ def has_linked_issue(token, owner, name, number): |
68 | 85 | return data["repository"]["pullRequest"]["closingIssuesReferences"]["totalCount"] > 0 |
69 | 86 |
|
70 | 87 |
|
| 88 | +def author_checkbox_checked(pr): |
| 89 | + return bool(AUTHOR_CHECKBOX_PATTERN.search(pr.body or "")) |
| 90 | + |
| 91 | + |
71 | 92 | def has_existing_reminder(pr): |
72 | 93 | return any(REMINDER_MARKER in (c.body or "") for c in pr.get_issue_comments()) |
73 | 94 |
|
74 | 95 |
|
| 96 | +def is_privileged_author(repo, pr, author): |
| 97 | + """Return True if the author is a maintainer, has write/admin access, or is a collaborator.""" |
| 98 | + # `author_association` is on the PR payload and needs no extra token scope. |
| 99 | + association = (pr.raw_data or {}).get("author_association") |
| 100 | + if association in PRIVILEGED_ASSOCIATIONS: |
| 101 | + return True |
| 102 | + # Fall back to the collaborator-permission API to catch writers/collaborators |
| 103 | + # whose association is reported as CONTRIBUTOR/NONE on this particular PR. |
| 104 | + try: |
| 105 | + permission = repo.get_collaborator_permission(author) |
| 106 | + except Exception as e: |
| 107 | + # A 404 here means the user is not a collaborator at all (external contributor). |
| 108 | + logger.info("Could not resolve permission for @%s, treating as external: %s", author, e) |
| 109 | + return False |
| 110 | + return permission in PRIVILEGED_PERMISSIONS |
| 111 | + |
| 112 | + |
75 | 113 | def reminder_body(author): |
76 | 114 | return ( |
77 | 115 | f"{REMINDER_MARKER}\n" |
@@ -109,9 +147,13 @@ def main(): |
109 | 147 | author = pr.user.login |
110 | 148 | if not author or author.endswith("[bot]") or pr.user.type == "Bot": |
111 | 149 | continue |
| 150 | + if is_privileged_author(repo, pr, author): |
| 151 | + continue |
112 | 152 | labels = {label.name for label in pr.labels} |
113 | 153 | if labels & BYPASS_LABELS: |
114 | 154 | continue |
| 155 | + if author_checkbox_checked(pr): |
| 156 | + continue |
115 | 157 | if has_linked_issue(token, owner, name, pr.number): |
116 | 158 | continue |
117 | 159 | if has_existing_reminder(pr): |
|
0 commit comments