Skip to content

Commit 3864506

Browse files
authored
Merge pull request #53 from lagru/multiple-summaries
Allow multiple summaries per pull request
2 parents 92fb0ed + 13a7303 commit 3864506

File tree

12 files changed

+582
-80
lines changed

12 files changed

+582
-80
lines changed

.github/workflows/test.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: test
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
pytest:
7+
runs-on: ${{ matrix.os }}-latest
8+
strategy:
9+
matrix:
10+
os: [ubuntu]
11+
python-version: ["3.9", "3.10", "3.11"]
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Set up Python ${{ matrix.python-version }}
16+
uses: actions/setup-python@v4
17+
with:
18+
python-version: ${{ matrix.python-version }}
19+
20+
- uses: actions/cache@v3
21+
with:
22+
path: ~/.cache/pip
23+
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
24+
restore-keys: |
25+
${{ runner.os }}-pip-
26+
27+
- name: Install dependencies
28+
run: |
29+
python -m pip install --upgrade pip
30+
pip install .[test]
31+
32+
- name: Test
33+
run: pytest --cov

README.md

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ https://github.com/scientific-python/changelist/blob/main/CHANGELOG.md.
1010
- Compile a list of pull requests, code authors, and reviewers between
1111
two given git commits.
1212
- Categorize pull requests into sections based on GitHub labels.
13+
- Override pull request titles with more descriptive summaries.
14+
- Document unrelated changes in a pull requests in separate summaries.
1315

1416
_This project is currently in its alpha stage and might be incomplete or change a lot!_
1517

@@ -33,9 +35,36 @@ changelist scientific-python/changelist v0.2.0 main
3335

3436
This will list all pull requests, authors and reviewers that touched commits
3537
between `v0.2.0` and `main` (excluding `v0.2.0`).
36-
Pull requests are sorted into section according to the configuration in
38+
Pull requests are sorted into sections according to the configuration in
3739
`tool.changelist.label_section_map`.
3840

41+
## Writing pull request summaries
42+
43+
By default, changelist will fall back to the title of a pull request and its
44+
GitHub labels to sort it into the appropriate section. However, if you want
45+
longer summaries of your changes you can add a code block with the following
46+
form anywhere in the description of the pull request:
47+
48+
```release-note
49+
An ideally expressive description of the change that is included as
50+
a single bullet point. Newlines are removed.
51+
```
52+
53+
Sometimes pull requests introduce multiple changes that should be listed in different
54+
sections. For that reason, a summary block like above can be used more than
55+
once. Additionally, you can add independent labels to each summary by adding a
56+
`{label="..."}` anywhere in the summary. These labels are sorted the same way
57+
as regular pull request labels are. E.g. the two summaries below will go into
58+
separate sections:
59+
60+
```release-note {label="Bug fix"}
61+
Make `is_odd()` work for negative numbers.
62+
```
63+
64+
```release-note
65+
Deprecate `ìs_odd`; use `not (x % 2)` instead! {label="API, Highlight"}
66+
```
67+
3968
## Configuration
4069

4170
changelist can be configured from two sources, in order of precedence:
@@ -79,18 +108,39 @@ ignored_user_logins = [
79108
]
80109

81110
# If this regex matches a pull requests description, the captured content
82-
# is included instead of the pull request title.
83-
# E.g. the default regex below is matched by
111+
# is included instead of the pull request title. E.g. the
112+
# default regex below is matched by
84113
#
85114
# ```release-note
86-
# An ideally expressive description of the change that is included as a single
87-
# bullet point. Newlines are removed.
115+
# An ideally expressive description of the change that is included as
116+
# a single bullet point. Newlines are removed.
88117
# ```
89118
#
90119
# If you modify this regex, make sure to match the content with a capture
91-
# group named "summary".
120+
# group named "summary". The regex is allowed to match more than once in which
121+
# case a single pull request may result in multiple items (see
122+
# `pr_summary_label_regex` for why that might be useful).
92123
pr_summary_regex = "^```release-note\\s*(?P<summary>[\\s\\S]*?\\w[\\s\\S]*?)\\s*^```"
93124

125+
# Sometimes pull requests introduce changes that should be listed in different
126+
# sections. For that reason, `pr_summary_regex` can match more than once and
127+
# this regex, `pr_summary_label_regex`, can be used to add independent labels
128+
# to each summary. These labels are sorted with the `label_section_map` the
129+
# same way as regular pull request labels are. E.g. the example below will both
130+
# match and go into separate sections:
131+
#
132+
# ```release-note {label="Bug fix"}
133+
# Make `is_odd()` work for negative numbers.
134+
# ```
135+
#
136+
# ```release-note
137+
# Deprecate `ìs_odd`; use `not (x % 2)` instead! {label="API, Highlight"}
138+
# ```
139+
#
140+
# If you modify this regex, make sure to match the content with a capture
141+
# group named "label".
142+
pr_summary_label_regex = """{[^}]*?label=[\\"](?P<label>[^\\"]+)[^}]*?}"""
143+
94144
# If any of a pull request's labels matches one of the regexes on the left side
95145
# its summary will appear in the appropriate section with the title given on
96146
# the right side. If a pull request doesn't match one of these categories it is

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ build-backend = "setuptools.build_meta"
55
[project]
66
name = "changelist"
77
version = "0.5rc0.dev0"
8+
# TODO add slots=True, kw_only=True to dataclasses starting with >=3.10
89
requires-python = ">=3.9"
910
readme = "README.md"
1011
license = {file = "LICENSE.txt"}
@@ -33,6 +34,7 @@ changelist = "changelist.__main__:main"
3334

3435
[project.optional-dependencies]
3536
lint = ["pre-commit == 3.6.0"]
37+
test = ["pytest", "pytest-cov"]
3638

3739
[tool.ruff]
3840
line-length = 88
@@ -46,3 +48,8 @@ select = [
4648
"I",
4749
"UP",
4850
]
51+
52+
[tool.pytest.ini_options]
53+
minversion = "6.0"
54+
addopts = "--doctest-modules"
55+
testpaths = ["src", "test"]

src/changelist/_cli.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import argparse
22
import logging
33
import os
4-
import re
54
import sys
65
import tempfile
76
from pathlib import Path
@@ -13,6 +12,7 @@
1312

1413
from ._config import add_config_defaults, local_config, remote_config
1514
from ._format import MdFormatter, RstFormatter
15+
from ._objects import ChangeNote, Contributor
1616
from ._query import commits_between, contributors, pull_requests_from_commits
1717

1818
logger = logging.getLogger(__name__)
@@ -152,18 +152,24 @@ def main(
152152
pull_requests=lazy_tqdm(pull_requests, desc="Fetching reviewers"),
153153
)
154154

155+
print("Formatting notes...", file=sys.stderr)
156+
change_notes = ChangeNote.from_pull_requests(
157+
pull_requests,
158+
pr_summary_regex=config["pr_summary_regex"],
159+
pr_summary_label_regex=config["pr_summary_label_regex"],
160+
)
161+
155162
Formatter = {"md": MdFormatter, "rst": RstFormatter}[format]
156163
formatter = Formatter(
157164
repo_name=org_repo.split("/")[-1],
158-
pull_requests=pull_requests,
159-
authors=authors,
160-
reviewers=reviewers,
165+
change_notes=change_notes,
166+
authors=Contributor.from_named_users(authors),
167+
reviewers=Contributor.from_named_users(reviewers),
161168
version=version,
162169
title_template=config["title_template"],
163170
intro_template=config["intro_template"],
164171
outro_template=config["outro_template"],
165172
label_section_map=config["label_section_map"],
166-
pr_summary_regex=re.compile(config["pr_summary_regex"], flags=re.MULTILINE),
167173
ignored_user_logins=config["ignored_user_logins"],
168174
)
169175

@@ -174,7 +180,5 @@ def main(
174180
io.writelines(formatter.iter_lines())
175181
else:
176182
print()
177-
for line in formatter.iter_lines():
178-
assert line.endswith("\n")
179-
assert line.count("\n") == 1
180-
print(line, end="", file=sys.stdout)
183+
notes = str(formatter)
184+
print(notes, file=sys.stdout)

0 commit comments

Comments
 (0)