Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Fixes # (issue)
[documentation guidelines](https://github.com/huggingface/diffusers/tree/main/docs), and
[here are tips on formatting docstrings](https://github.com/huggingface/diffusers/tree/main/docs#writing-source-documentation).
- [ ] Did you write any new necessary tests?
- [ ] Are you the author (or part of the team) of the model/pipeline (only applicable for model/pipeline related PRs)?


## Who can review?
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/pr_link_issue_reminder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ on:
workflow_dispatch:

jobs:
remind_or_close:
name: Remind or close PRs without a linked issue
remind:
# Reminds external contributors to link an issue. PRs from maintainers, users
# with write/admin access, and collaborators are skipped by the script.
name: Remind external contributors to link an issue
if: github.repository == 'huggingface/diffusers'
runs-on: ubuntu-22.04
permissions:
Expand Down
42 changes: 42 additions & 0 deletions utils/remind_link_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@
- If a PR is not linked and no prior reminder is present, the script posts a single
friendly reminder comment.
- PRs labeled `no-issue-needed` and bot-authored PRs are skipped.
- PRs authored by maintainers, users with write (or admin) access, and collaborators
are skipped; the reminder only targets external contributors.
"""

import logging
import os
import re
from datetime import datetime, timedelta, timezone

import requests
Expand All @@ -37,6 +40,20 @@
REMINDER_MARKER = "<!-- pr-link-issue-reminder -->"
BYPASS_LABELS = {"no-issue-needed"}
LOOKBACK_DAYS = 2
# Collaborator permission levels that mark a PR author as a maintainer / writer /
# collaborator. Authors with any of these are skipped (the reminder is only for
# external contributors).
PRIVILEGED_PERMISSIONS = {"admin", "write", "maintain", "triage"}

# `author_association` values that mark the author as a maintainer / collaborator.
# These are available on the PR payload without needing extra token scopes.
PRIVILEGED_ASSOCIATIONS = {"OWNER", "MEMBER", "COLLABORATOR"}

# A PR authored by the model/pipeline's own team does not need to link an issue.
# Matches a checked task-list item for the corresponding PR template checkbox.
AUTHOR_CHECKBOX_PATTERN = re.compile(
r"-\s*\[\s*[xX]\s*\]\s*Are you the author \(or part of the team\) of the model/pipeline"
)
CONTRIBUTION_GUIDE_URL = "https://huggingface.co/docs/diffusers/main/en/conceptual/contribution#coding-with-ai-agents"

GRAPHQL_URL = "https://api.github.com/graphql"
Expand Down Expand Up @@ -68,10 +85,31 @@ def has_linked_issue(token, owner, name, number):
return data["repository"]["pullRequest"]["closingIssuesReferences"]["totalCount"] > 0


def author_checkbox_checked(pr):
return bool(AUTHOR_CHECKBOX_PATTERN.search(pr.body or ""))


def has_existing_reminder(pr):
return any(REMINDER_MARKER in (c.body or "") for c in pr.get_issue_comments())


def is_privileged_author(repo, pr, author):
"""Return True if the author is a maintainer, has write/admin access, or is a collaborator."""
# `author_association` is on the PR payload and needs no extra token scope.
association = (pr.raw_data or {}).get("author_association")
if association in PRIVILEGED_ASSOCIATIONS:
return True
# Fall back to the collaborator-permission API to catch writers/collaborators
# whose association is reported as CONTRIBUTOR/NONE on this particular PR.
try:
permission = repo.get_collaborator_permission(author)
except Exception as e:
# A 404 here means the user is not a collaborator at all (external contributor).
logger.info("Could not resolve permission for @%s, treating as external: %s", author, e)
return False
return permission in PRIVILEGED_PERMISSIONS


def reminder_body(author):
return (
f"{REMINDER_MARKER}\n"
Expand Down Expand Up @@ -109,9 +147,13 @@ def main():
author = pr.user.login
if not author or author.endswith("[bot]") or pr.user.type == "Bot":
continue
if is_privileged_author(repo, pr, author):
continue
labels = {label.name for label in pr.labels}
if labels & BYPASS_LABELS:
continue
if author_checkbox_checked(pr):
continue
if has_linked_issue(token, owner, name, pr.number):
continue
if has_existing_reminder(pr):
Expand Down
Loading