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