Skip to content

Commit 4e4a506

Browse files
Ignore PRs by specified usernames in the generated changelist (#85)
By default, hide pull requests that are authored by users / bots given in the config field `ignored_user_logins. This behavior can be overridden by explicitly specifying the new config field `ignore_prs_by_username`. --------- Co-authored-by: Lars Grüter <lagru@mailbox.org>
1 parent ab32fa5 commit 4e4a506

6 files changed

Lines changed: 101 additions & 5 deletions

File tree

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,19 @@ _These lists are automatically generated, and may not be complete or may contain
102102
duplicates._
103103
"""
104104

105-
# Profiles that are excluded from the contributor list.
105+
# Usernames that are excluded from the contributor list. This may also ignore
106+
# pull request of these users (see field `ignore_prs_by_username`).
106107
ignored_user_logins = [
107108
"web-flow",
108109
]
109110

111+
# Ignore pull requests authored by these users.
112+
# If `{include = "ignored_user_logins"}` is included in this list (the default),
113+
# usernames from the field `ignored_user_logins` are also included.
114+
ignore_prs_by_username = [
115+
{include = "ignored_user_logins"},
116+
]
117+
110118
# If this regex matches a pull requests description, the captured content
111119
# is included instead of the pull request title. E.g. the
112120
# default regex below is matched by
@@ -228,7 +236,7 @@ jobs:
228236
name: attach to PR
229237
runs-on: ubuntu-latest
230238
steps:
231-
- uses: scientific-python/attach-next-milestone-action@bc07be829f693829263e57d5e8489f4e57d3d420
239+
- uses: scientific-python/attach-next-milestone-action@c9cfab10ad0c67fed91b01103db26b7f16634639
232240
with:
233241
token: ${{ secrets.MILESTONE_LABELER_TOKEN }}
234242
force: true

src/changelist/_cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def main(
157157
pull_requests,
158158
pr_summary_regex=config["pr_summary_regex"],
159159
pr_summary_label_regex=config["pr_summary_label_regex"],
160+
ignore_prs_by_username=config["ignore_prs_by_username"],
160161
)
161162

162163
Formatter = {"md": MdFormatter, "rst": RstFormatter}[format]

src/changelist/_config.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,28 @@
1313
DEFAULT_CONFIG_PATH = Path(__file__).parent / "default_config.toml"
1414

1515

16+
def _dereference_ignore_prs_by_username(config: dict) -> dict:
17+
"""Dereference "include" in `ignore_prs_by_username` field.
18+
19+
Example:
20+
>>> config = {
21+
... "ignored_user_logins": ["bot"],
22+
... "ignore_prs_by_username": [{"include": "ignored_user_logins"}, "web-flow"]
23+
... }
24+
>>> _dereference_ignore_prs_by_username(config)
25+
{'ignored_user_logins': ['bot'], 'ignore_prs_by_username': ['web-flow', 'bot']}
26+
"""
27+
if "ignore_prs_by_username" not in config:
28+
return config
29+
30+
include_sentinel = {"include": "ignored_user_logins"}
31+
if include_sentinel in config["ignore_prs_by_username"]:
32+
config["ignore_prs_by_username"].remove(include_sentinel)
33+
config["ignore_prs_by_username"] += config.get("ignored_user_logins", [])
34+
35+
return config
36+
37+
1638
def remote_config(gh: Github, org_repo: str, *, rev: str):
1739
"""Return configuration options in remote pyproject.toml if they exist."""
1840
repo = gh.get_repo(org_repo)
@@ -50,4 +72,5 @@ def add_config_defaults(
5072
if key not in config:
5173
config[key] = value
5274
logger.debug("using default config value for %s", key)
75+
config = _dereference_ignore_prs_by_username(config)
5376
return config

src/changelist/_objects.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import re
33
from dataclasses import dataclass
44
from datetime import datetime
5-
from typing import Union
5+
from typing import Optional, Union
66

77
from github.NamedUser import NamedUser
88
from github.PullRequest import PullRequest
@@ -27,6 +27,7 @@ def from_pull_requests(
2727
*,
2828
pr_summary_regex: str,
2929
pr_summary_label_regex: str,
30+
ignore_prs_by_username: Optional[list[str]] = None,
3031
) -> "set[ChangeNote]":
3132
"""Create a set of notes from pull requests.
3233
@@ -39,12 +40,25 @@ def from_pull_requests(
3940
requests and notes somewhat. While ideally, a pull request introduces
4041
a change that would be described in a single note, this is often not
4142
the case.
43+
44+
`ignore_prs_by_username` is a list of user logins whose pull requests
45+
should be excluded from the changelog entirely.
4246
"""
47+
if ignore_prs_by_username is None:
48+
ignore_prs_by_username = []
49+
4350
pr_summary_regex = re.compile(pr_summary_regex, flags=re.MULTILINE)
4451
pr_summary_label_regex = re.compile(pr_summary_label_regex)
4552

4653
notes = set()
4754
for pr in pull_requests:
55+
if pr.user and pr.user.login in ignore_prs_by_username:
56+
logger.debug(
57+
"skipping PR %s from ignored user %s",
58+
pr.html_url,
59+
pr.user.login,
60+
)
61+
continue
4862
pr_labels = tuple(label.name for label in pr.labels)
4963

5064
if not pr.body or not (

src/changelist/default_config.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,19 @@ _These lists are automatically generated, and may not be complete or may contain
2121
duplicates._
2222
"""
2323

24-
# Profiles that are excluded from the contributor list.
24+
# Usernames that are excluded from the contributor list. This may also ignore
25+
# pull request of these users (see field `ignore_prs_by_username`).
2526
ignored_user_logins = [
2627
"web-flow",
2728
]
2829

30+
# Ignore pull requests authored by these users.
31+
# If `{include = "ignored_user_logins"}` is included in this list (the default),
32+
# usernames from the field `ignored_user_logins` are also included.
33+
ignore_prs_by_username = [
34+
{include = "ignored_user_logins"},
35+
]
36+
2937
# If this regex matches a pull requests description, the captured content
3038
# is included instead of the pull request title. E.g. the
3139
# default regex below is matched by

test/test_objects.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from dataclasses import dataclass
1+
from dataclasses import dataclass, field
22
from datetime import datetime
33
from pathlib import Path
44
from typing import Union
@@ -12,6 +12,13 @@
1212
DEFAULT_CONFIG = local_config(DEFAULT_CONFIG_PATH)
1313

1414

15+
@dataclass
16+
class _MockUser:
17+
"""Mocks github.User partially."""
18+
19+
login: str
20+
21+
1522
@dataclass
1623
class _MockLabel:
1724
"""Mocks github.Label.Label partially."""
@@ -26,6 +33,7 @@ class _MockPullRequest:
2633
title: str
2734
body: Union[str, None]
2835
labels: list[_MockLabel]
36+
user: _MockUser = field(default_factory=lambda: _MockUser(login="friendlyDev"))
2937
number: int = (42,)
3038
html_url: str = "https://github.com/scientific-python/changelist/pull/53"
3139
merged_at: datetime = datetime(2024, 1, 1)
@@ -73,6 +81,40 @@ def test_from_pull_requests_multiple(self, caplog):
7381
assert caplog.records[0].levelname == "DEBUG"
7482
assert "falling back to PR labels for summary" in caplog.records[0].msg
7583

84+
def test_from_pull_requests_ignore_by_username(self, caplog):
85+
caplog.set_level("DEBUG")
86+
pull_requests = [
87+
_MockPullRequest(
88+
title="PR from user",
89+
body=None,
90+
labels=[_MockLabel("Documentation")],
91+
user=_MockUser("someDev"),
92+
),
93+
_MockPullRequest(
94+
title="PR from bot",
95+
body=None,
96+
labels=[_MockLabel("Documentation")],
97+
user=_MockUser("bot"),
98+
),
99+
]
100+
notes = ChangeNote.from_pull_requests(
101+
pull_requests,
102+
pr_summary_regex=DEFAULT_CONFIG["pr_summary_regex"],
103+
pr_summary_label_regex=DEFAULT_CONFIG["pr_summary_label_regex"],
104+
ignore_prs_by_username=["bot"],
105+
)
106+
assert len(notes) == 1
107+
notes = list(notes)
108+
assert notes[0].content == "PR from user"
109+
assert notes[0].labels == ("Documentation",)
110+
111+
assert len(caplog.records) == 2
112+
assert caplog.records[0].levelname == "DEBUG"
113+
assert "falling back to title" in caplog.records[0].msg
114+
115+
assert caplog.records[1].levelname == "DEBUG"
116+
assert "skipping PR %s from ignored user %s" in caplog.records[1].msg
117+
76118
def test_from_pull_requests_fallback_title(self, caplog):
77119
caplog.set_level("DEBUG")
78120
pull_request = _MockPullRequest(

0 commit comments

Comments
 (0)