-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
First draft for automatic generation of changelog entries #6875
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8bb130a
2d0bc7f
8faf3a7
ddd6407
fe69e00
32c0e10
2d1803d
dd6a33f
269ff42
8749f70
9465cfd
a03d032
3e6de08
94bf3af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: changelog | ||
|
||
on: | ||
pull_request: | ||
types: [opened, synchronize, labeled, unlabeled, reopened, edited] | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
build: | ||
name: Changelog Entry Check | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Emit warning if changelog is missing | ||
if: | ||
contains(github.event.pull_request.labels.*.name, 'skip news :mute:') != true | ||
&& (contains(github.event.pull_request.body, '<!-- changelog start -->') != | ||
true || contains(github.event.pull_request.body, '<!-- changelog end -->') != | ||
true) | ||
run: | | ||
echo "Please add \n\n<!-- changelog start -->\nYOUR CHANGELOG ENTRY\n<!-- changelog end -->\n\n to the description of the PR \ | ||
(or if appropriate, ask a maintainer to add the 'skip news' label)" && \ | ||
exit 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,7 @@ | |
"pylint_extensions", | ||
"pylint_messages", | ||
"pylint_options", | ||
"pylint_changelog", | ||
"sphinx.ext.autosectionlabel", | ||
"sphinx.ext.intersphinx", | ||
"sphinx_reredirects", | ||
|
@@ -297,3 +298,13 @@ | |
autosectionlabel_prefix_document = True | ||
|
||
linkcheck_ignore = ["https://github.com/PyCQA/pylint/blob/main/pylint/extensions/.*"] | ||
|
||
|
||
# -- Options for pylint_changelog extension ------------------------------------ | ||
|
||
pylint_changelog_user = "PyCQA" | ||
pylint_changelog_project = "pylint" | ||
pylint_changelog_token = os.getenv("GITHUB_TOKEN") | ||
pylint_changelog_exclude_labels = [ | ||
"Documentation π", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding this: I said in the original issue "If the PR is labelled maintenance, primer or documentation in github then nothing is required". But I also added 'skip news π', because there's some condition to take into account imo. documentation alone should be skipped but what about documentation and bug together or primer and something else ... ? Maybe just one label with clear expectation is better ? We're doing a lot of maintenance/documentation issue that will need to have one more label if we do that though. I'm thinking about "skip news" or (maintenance, primer or documentation) but only that label with no other, right now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I too think that an explicit "skip news" label would be the cleanest way to go. |
||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
#!/usr/bin/env python | ||
|
||
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html | ||
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE | ||
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt | ||
|
||
"""Custom extension to automatically generate changelog sections | ||
from GitHub issues. | ||
""" | ||
from __future__ import annotations | ||
|
||
import re | ||
from collections.abc import Iterable | ||
|
||
from docutils import frontend, nodes | ||
from docutils.parsers.rst import directives | ||
from docutils.utils import new_document | ||
from github3 import login # type: ignore[import] | ||
from github3.search.issue import IssueSearchResult # type: ignore[import] | ||
from myst_parser.docutils_ import Parser # type: ignore[import] | ||
from sphinx.application import Sphinx | ||
from sphinx.util import logging | ||
from sphinx.util.docutils import SphinxDirective | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class ChangelogDirective(SphinxDirective): | ||
option_spec = { | ||
"query": directives.unchanged_required, | ||
"caption": directives.unchanged, | ||
"hide_if_empty": directives.flag, | ||
} | ||
has_content = True | ||
parser = Parser() | ||
changelog_pattern = re.compile( | ||
r"<!-- changelog start -->\n(?P<entry>(.*\n)*)<!-- changelog end -->", | ||
flags=re.MULTILINE, | ||
) | ||
|
||
def run(self): | ||
if not self.config.pylint_changelog_token: | ||
logger.info( | ||
"No Github token provided. Changelog generation will be skipped." | ||
) | ||
return [] | ||
result = [] | ||
caption = self.options.get("caption") | ||
if caption: | ||
result.append(nodes.title(text=caption)) | ||
list_node = nodes.bullet_list( | ||
"", | ||
*( | ||
self._build_changelog_entry(issue) | ||
for issue in self._get_relevant_issues() | ||
), | ||
) | ||
result.append(list_node) | ||
logger.info("Found %d issues for this query.", len(list_node)) | ||
if not list_node and self.options.get("hide_if_empty", False): | ||
logger.info("Flag 'hide_if_empty' is set, hiding this section.") | ||
return [] | ||
return result | ||
|
||
def _get_relevant_issues(self) -> Iterable[IssueSearchResult]: | ||
full_query = self._build_query() | ||
gh = login(token=self.config.pylint_changelog_token) | ||
logger.info("Searching for issues/pull requests matching query %s", full_query) | ||
return gh.search_issues(query=full_query) | ||
|
||
def _build_query(self) -> str: | ||
user = self.config.pylint_changelog_user | ||
project = self.config.pylint_changelog_project | ||
query = self.options.get("query") | ||
full_query = f"repo:{user}/{project} {query}" | ||
for excluded_label in self.config.pylint_changelog_exclude_labels: | ||
full_query += f' -label:"{excluded_label}"' | ||
return full_query | ||
|
||
def _build_changelog_entry(self, issue: IssueSearchResult) -> nodes.list_item: | ||
match = self.changelog_pattern.search(issue.body) | ||
if match: | ||
text = match.group("entry").strip() | ||
else: | ||
logger.warning( | ||
"PR #%d is missing the changelog section. " | ||
"Using the PR title as substitute.", | ||
issue.number, | ||
) | ||
text = issue.title | ||
text += f"\n\nPR: [#{issue.number}](https://github.com/PyCQA/pylint/pull/{issue.number})" | ||
return nodes.list_item("", *self._parse_markdown(text).children) | ||
|
||
def _parse_markdown(self, text: str) -> nodes.document: | ||
parser = Parser() | ||
components = (Parser,) | ||
settings = frontend.OptionParser(components=components).get_default_values() | ||
document = new_document("", settings=settings) | ||
parser.parse(text, document) | ||
return document | ||
|
||
|
||
def setup(app: Sphinx) -> dict[str, str | bool]: | ||
app.add_config_value("pylint_changelog_user", None, "html") | ||
app.add_config_value("pylint_changelog_project", None, "html") | ||
app.add_config_value("pylint_changelog_token", None, "html") | ||
app.add_config_value("pylint_changelog_exclude_labels", [], "html") | ||
app.add_directive("changelog", ChangelogDirective) | ||
return {"version": "0.1", "parallel_read_safe": True} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
Sphinx==5.0.1 | ||
sphinx-reredirects<1 | ||
github3.py~=3.2 | ||
myst-parser~=0.18 | ||
furo==2022.6.4.1 | ||
-e . |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.