Skip to content

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

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ To ease the process of reviewing your PR, do make sure to complete the following
| βœ“ | :hammer: Refactoring |
| βœ“ | :scroll: Docs |

## Changelog

<!--
Provide a short summary of the change, focused on the user-facing aspects.
Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas Jun 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Provide a short summary of the change, focused on the user-facing aspects.
Provide a short summary of the change, focused on the user-facing aspects.
If the change does not need to be documented ask a maintainer to add the
'skip news πŸ”‡' label.

This summary will be included in the changelog. Please do not remove the
"changelog start" and "changelog end" lines, as they are used by the script
generating the changelog.
If your PR does not need a changelog entry, ask a contributor to add the
'skip news :mute:' label.
-->
<!-- changelog start -->

<!-- changelog end -->

## Description

<!-- If this PR references an issue without fixing it: -->
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/changelog.yml
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
10 changes: 8 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ repos:
rev: "v6.0.0.post1"
hooks:
- id: rstcheck
args: ["--report-level=warning"]
args: ["--report-level=warning", "--ignore-directives=changelog"]
files: ^(doc/(.*/)*.*\.rst)
additional_dependencies: [Sphinx==5.0.1]
- repo: https://github.com/pre-commit/mirrors-mypy
Expand All @@ -102,7 +102,13 @@ repos:
types: [python]
args: []
require_serial: true
additional_dependencies: ["platformdirs==2.2.0", "types-pkg_resources==0.1.3"]
additional_dependencies:
[
"platformdirs==2.2.0",
"types-pkg_resources==0.1.3",
"types-docutils==0.18.3",
"github3.py~=3.2",
]
exclude: tests(/\w*)*/functional/|tests/input|tests(/.*)+/conftest.py|doc/data/messages|tests(/\w*)*data/
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.6.2
Expand Down
11 changes: 11 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"pylint_extensions",
"pylint_messages",
"pylint_options",
"pylint_changelog",
"sphinx.ext.autosectionlabel",
"sphinx.ext.intersphinx",
"sphinx_reredirects",
Expand Down Expand Up @@ -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 πŸ“–",
Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas Jun 6, 2022

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.
Same for the "Other changes" section.

]
109 changes: 109 additions & 0 deletions doc/exts/pylint_changelog.py
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}
1 change: 1 addition & 0 deletions doc/requirements.txt
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 .
73 changes: 24 additions & 49 deletions doc/whatsnew/2/2.15/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,34 @@
Summary -- Release highlights
=============================

.. changelog::
:caption: New checkers
:query: is:closed is:pr milestone:2.15.0 label:"New checker ✨"

New checkers
============
.. changelog::
:caption: Removed checkers
:query: is:closed is:pr milestone:2.15.0 label:"Removed checker ❌"

.. changelog::
:caption: Extensions
:query: is:closed is:pr milestone:2.15.0 label:"Extension"

Removed checkers
================
.. changelog::
:caption: False positives fixed
:query: is:closed is:pr milestone:2.15.0 label:"False Positive 🦟"

.. changelog::
:caption: False negatives fixed
:query: is:closed is:pr milestone:2.15.0 label:"False Negative πŸ¦‹"

Extensions
==========
.. changelog::
:caption: Other bug fixes
:query: is:closed is:pr milestone:2.15.0 label:"Bug πŸͺ³" -label:"False Negative πŸ¦‹" -label:"False Positive 🦟"

.. changelog::
:caption: Other Changes
:query: is:closed is:pr milestone:2.15.0 -label:"False Negative πŸ¦‹" -label:"False Positive 🦟" -label:"Bug πŸͺ³" -label:"New checker ✨" -label:"Removed checker ❌"

False positives fixed
=====================


False negatives fixed
=====================

* Emit ``modified-iterating-list`` and analogous messages for dicts and sets when iterating
literals, or when using the ``del`` keyword.

Closes #6648

* Emit ``using-constant-test`` when testing the truth value of a variable or call result
holding a generator.

Closes #6909

* Emit ``used-before-assignment`` for self-referencing named expressions (``:=``) lacking
prior assignments.

Closes #5653


Other bug fixes
===============


Other Changes
=============


Internal changes
================

* ``pylint.testutils.primer`` is now a private API.

Refs #6905

* Fixed an issue where it was impossible to update functional tests output when the existing
output was impossible to parse. Instead of raising an error we raise a warning message and
let the functional test fail with a default value.

Refs #6891
.. changelog::
:caption: Internal changes
:query: is:closed is:pr milestone:2.15.0 label:"Maintenance"