Skip to content

Commit ac79467

Browse files
committed
tc
1 parent 8ff57d2 commit ac79467

File tree

6 files changed

+1603
-24
lines changed

6 files changed

+1603
-24
lines changed

.github/merge_rules.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
- name: superuser
2+
patterns:
3+
- '*'
4+
approved_by:
5+
- pytorch/metamates
6+
mandatory_checks_name:
7+
- Facebook CLA Check

.github/scripts/github_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,12 @@ def gh_update_pr_state(org: str, repo: str, pr_num: int, state: str = "open") ->
202202
)
203203
else:
204204
raise
205+
206+
207+
def gh_query_issues_by_labels(
208+
org: str, repo: str, labels: List[str], state: str = "open"
209+
) -> List[Dict[str, Any]]:
210+
url = f"{GITHUB_API_URL}/repos/{org}/{repo}/issues"
211+
return gh_fetch_json(
212+
url, method="GET", params={"labels": ",".join(labels), "state": state}
213+
)

.github/scripts/label_utils.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""GitHub Label Utilities."""
2+
3+
import json
4+
5+
from functools import lru_cache
6+
from typing import Any, List, Tuple, TYPE_CHECKING, Union
7+
8+
from github_utils import gh_fetch_url_and_headers, GitHubComment
9+
10+
# TODO: this is a temp workaround to avoid circular dependencies,
11+
# and should be removed once GitHubPR is refactored out of trymerge script.
12+
if TYPE_CHECKING:
13+
from trymerge import GitHubPR
14+
15+
BOT_AUTHORS = ["github-actions", "pytorchmergebot", "pytorch-bot"]
16+
17+
LABEL_ERR_MSG_TITLE = "This PR needs a `release notes:` label"
18+
LABEL_ERR_MSG = f"""# {LABEL_ERR_MSG_TITLE}
19+
If your changes are user facing and intended to be a part of release notes, please use a label starting with `release notes:`.
20+
21+
If not, please add the `topic: not user facing` label.
22+
23+
To add a label, you can comment to pytorchbot, for example
24+
`@pytorchbot label "topic: not user facing"`
25+
26+
For more information, see
27+
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.
28+
"""
29+
30+
31+
def request_for_labels(url: str) -> Tuple[Any, Any]:
32+
headers = {"Accept": "application/vnd.github.v3+json"}
33+
return gh_fetch_url_and_headers(
34+
url, headers=headers, reader=lambda x: x.read().decode("utf-8")
35+
)
36+
37+
38+
def update_labels(labels: List[str], info: str) -> None:
39+
labels_json = json.loads(info)
40+
labels.extend([x["name"] for x in labels_json])
41+
42+
43+
def get_last_page_num_from_header(header: Any) -> int:
44+
# Link info looks like: <https://api.github.com/repositories/65600975/labels?per_page=100&page=2>;
45+
# rel="next", <https://api.github.com/repositories/65600975/labels?per_page=100&page=3>; rel="last"
46+
link_info = header["link"]
47+
# Docs does not specify that it should be present for projects with just few labels
48+
# And https://github.com/malfet/deleteme/actions/runs/7334565243/job/19971396887 it's not the case
49+
if link_info is None:
50+
return 1
51+
prefix = "&page="
52+
suffix = ">;"
53+
return int(
54+
link_info[link_info.rindex(prefix) + len(prefix) : link_info.rindex(suffix)]
55+
)
56+
57+
58+
@lru_cache
59+
def gh_get_labels(org: str, repo: str) -> List[str]:
60+
prefix = f"https://api.github.com/repos/{org}/{repo}/labels?per_page=100"
61+
header, info = request_for_labels(prefix + "&page=1")
62+
labels: List[str] = []
63+
update_labels(labels, info)
64+
65+
last_page = get_last_page_num_from_header(header)
66+
assert (
67+
last_page > 0
68+
), "Error reading header info to determine total number of pages of labels"
69+
for page_number in range(2, last_page + 1): # skip page 1
70+
_, info = request_for_labels(prefix + f"&page={page_number}")
71+
update_labels(labels, info)
72+
73+
return labels
74+
75+
76+
def gh_add_labels(
77+
org: str, repo: str, pr_num: int, labels: Union[str, List[str]], dry_run: bool
78+
) -> None:
79+
if dry_run:
80+
print(f"Dryrun: Adding labels {labels} to PR {pr_num}")
81+
return
82+
gh_fetch_url_and_headers(
83+
url=f"https://api.github.com/repos/{org}/{repo}/issues/{pr_num}/labels",
84+
data={"labels": labels},
85+
)
86+
87+
88+
def gh_remove_label(
89+
org: str, repo: str, pr_num: int, label: str, dry_run: bool
90+
) -> None:
91+
if dry_run:
92+
print(f"Dryrun: Removing {label} from PR {pr_num}")
93+
return
94+
gh_fetch_url_and_headers(
95+
url=f"https://api.github.com/repos/{org}/{repo}/issues/{pr_num}/labels/{label}",
96+
method="DELETE",
97+
)
98+
99+
100+
def get_release_notes_labels(org: str, repo: str) -> List[str]:
101+
return [
102+
label
103+
for label in gh_get_labels(org, repo)
104+
if label.lstrip().startswith("release notes:")
105+
]
106+
107+
108+
def has_required_labels(pr: "GitHubPR") -> bool:
109+
pr_labels = pr.get_labels()
110+
# Check if PR is not user facing
111+
is_not_user_facing_pr = any(
112+
label.strip() == "topic: not user facing" for label in pr_labels
113+
)
114+
return is_not_user_facing_pr or any(
115+
label.strip() in get_release_notes_labels(pr.org, pr.project)
116+
for label in pr_labels
117+
)
118+
119+
120+
def is_label_err_comment(comment: GitHubComment) -> bool:
121+
# comment.body_text returns text without markdown
122+
no_format_title = LABEL_ERR_MSG_TITLE.replace("`", "")
123+
return (
124+
comment.body_text.lstrip(" #").startswith(no_format_title)
125+
and comment.author_login in BOT_AUTHORS
126+
)

0 commit comments

Comments
 (0)