Skip to content

Commit 887951a

Browse files
authored
Merge pull request #157 from nicoddemus/towncrier
Use towncrier and a custom tox environment to prepare a release
2 parents dad903c + 7161f97 commit 887951a

File tree

6 files changed

+203
-3
lines changed

6 files changed

+203
-3
lines changed

HOWTORELEASE.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
Release Procedure
22
-----------------
33

4-
#. Update the ``CHANGELOG.rst`` for the new release and commit.
4+
#. From a clean work tree, execute::
55

6-
#. Open a PR named ``release-X.Y.Z`` targeting ``master``.
6+
tox -e release -- VERSION
7+
8+
This will create the branch ready to be pushed.
9+
10+
#. Open a PR targeting ``master``.
711

812
#. All tests must pass and the PR must be approved by at least another maintainer.
913

@@ -16,5 +20,5 @@ Release Procedure
1620

1721
#. Make sure it is `available on PyPI <https://pypi.org/project/pluggy>`_.
1822

19-
#. Merge ``release-X.Y.Z`` into ``master``, either manually or using GitHub's web interface.
23+
#. Merge the PR into ``master``, either manually or using GitHub's web interface.
2024

changelog/README.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
This directory contains "newsfragments" which are short files that contain a small **ReST**-formatted
2+
text that will be added to the next ``CHANGELOG``.
3+
4+
The ``CHANGELOG`` will be read by users, so this description should be aimed to pytest users
5+
instead of describing internal changes which are only relevant to the developers.
6+
7+
Make sure to use full sentences with correct case and punctuation, for example::
8+
9+
Fix issue with non-ascii messages from the ``warnings`` module.
10+
11+
Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
12+
``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:
13+
14+
* ``feature``: new user facing features, like new command-line options and new behavior.
15+
* ``bugfix``: fixes a reported bug.
16+
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
17+
* ``removal``: feature deprecation or removal.
18+
* ``vendor``: changes in packages vendored in pytest.
19+
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
20+
21+
So for example: ``123.feature.rst``, ``456.bugfix.rst``.
22+
23+
If your PR fixes an issue, use that number here. If there is no issue,
24+
then after you submit the PR and get the PR number you can add a
25+
changelog using that instead.
26+
27+
If you are not sure what issue type to use, don't hesitate to ask in your PR.
28+
29+
``towncrier`` preserves multiple paragraphs and formatting (code blocks, lists, and so on), but for entries
30+
other than ``features`` it is usually better to stick to a single paragraph to keep it concise. You can install
31+
``towncrier`` and then run ``towncrier --draft``
32+
if you want to get a preview of how your change will look in the final release notes.

changelog/_template.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{% for section in sections %}
2+
{% set underline = "-" %}
3+
{% if section %}
4+
{{section}}
5+
{{ underline * section|length }}{% set underline = "~" %}
6+
7+
{% endif %}
8+
{% if sections[section] %}
9+
{% for category, val in definitions.items() if category in sections[section] %}
10+
11+
{{ definitions[category]['name'] }}
12+
{{ underline * definitions[category]['name']|length }}
13+
14+
{% if definitions[category]['showcontent'] %}
15+
{% for text, values in sections[section][category]|dictsort(by='value') %}
16+
{% set issue_joiner = joiner(', ') %}
17+
- {% for value in values|sort %}{{ issue_joiner() }}`{{ value }} <https://github.com/pytest-dev/pluggy/issues/{{ value[1:] }}>`_{% endfor %}: {{ text }}
18+
19+
20+
{% endfor %}
21+
{% else %}
22+
- {{ sections[section][category]['']|sort|join(', ') }}
23+
24+
25+
{% endif %}
26+
{% if sections[section][category]|length == 0 %}
27+
28+
No significant changes.
29+
30+
31+
{% else %}
32+
{% endif %}
33+
{% endfor %}
34+
{% else %}
35+
36+
No significant changes.
37+
38+
39+
{% endif %}
40+
{% endfor %}

pyproject.toml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[build-system]
2+
requires = [
3+
"setuptools",
4+
"setuptools-scm",
5+
"wheel",
6+
]
7+
8+
[tool.towncrier]
9+
package = "pluggy"
10+
package_dir = "pluggy"
11+
filename = "CHANGELOG.rst"
12+
directory = "changelog/"
13+
template = "changelog/_template.rst"
14+
15+
[[tool.towncrier.type]]
16+
directory = "removal"
17+
name = "Deprecations and Removals"
18+
showcontent = true
19+
20+
[[tool.towncrier.type]]
21+
directory = "feature"
22+
name = "Features"
23+
showcontent = true
24+
25+
[[tool.towncrier.type]]
26+
directory = "bugfix"
27+
name = "Bug Fixes"
28+
showcontent = true
29+
30+
[[tool.towncrier.type]]
31+
directory = "vendor"
32+
name = "Vendored Libraries"
33+
showcontent = true
34+
35+
[[tool.towncrier.type]]
36+
directory = "doc"
37+
name = "Improved Documentation"
38+
showcontent = true
39+
40+
[[tool.towncrier.type]]
41+
directory = "trivial"
42+
name = "Trivial/Internal Changes"
43+
showcontent = true

scripts/release.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
Release script.
3+
"""
4+
import argparse
5+
import sys
6+
from subprocess import check_call
7+
8+
from colorama import init, Fore
9+
from git import Repo, Remote
10+
11+
12+
def create_branch(version):
13+
"""Create a fresh branch from upstream/master"""
14+
repo = Repo.init('.')
15+
if repo.is_dirty(untracked_files=True):
16+
raise RuntimeError(f'Repository is dirty, please commit/stash your changes.')
17+
18+
branch_name = f"release-{version}"
19+
print(f"{Fore.CYAN}Create {branch_name} branch from upstream master")
20+
upstream = get_upstream(repo)
21+
upstream.fetch()
22+
release_branch = repo.create_head(branch_name, upstream.refs.master, force=True)
23+
release_branch.checkout()
24+
return repo
25+
26+
27+
def get_upstream(repo: Repo) -> Remote:
28+
"""Find upstream repository for pluggy on the remotes"""
29+
for remote in repo.remotes:
30+
for url in remote.urls:
31+
if url.endswith("pytest-dev/pluggy.git"):
32+
return remote
33+
raise RuntimeError("could not find tox-dev/tox.git remote")
34+
35+
36+
def pre_release(version):
37+
"""Generates new docs, release announcements and creates a local tag."""
38+
repo = create_branch(version)
39+
changelog(version, write_out=True)
40+
repo.index.add(['CHANGELOG.rst', 'changelog'])
41+
repo.index.commit(f"Preparing release {version}")
42+
43+
print()
44+
print(f"{Fore.GREEN}Please push your branch to your fork and open a PR.")
45+
46+
47+
def changelog(version, write_out=False):
48+
if write_out:
49+
addopts = []
50+
else:
51+
addopts = ["--draft"]
52+
print(f"{Fore.CYAN}Generating CHANGELOG")
53+
check_call(["towncrier", "--yes", "--version", version] + addopts)
54+
55+
56+
def main():
57+
init(autoreset=True)
58+
parser = argparse.ArgumentParser()
59+
parser.add_argument("version", help="Release version")
60+
options = parser.parse_args()
61+
try:
62+
pre_release(options.version)
63+
except RuntimeError as e:
64+
print(f'{Fore.RED}ERROR: {e}')
65+
return 1
66+
67+
68+
if __name__ == "__main__":
69+
sys.exit(main())

tox.ini

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,15 @@ filterwarnings =
4242

4343
[flake8]
4444
max-line-length=99
45+
46+
[testenv:release]
47+
decription = do a release, required posarg of the version number
48+
basepython = python3.6
49+
skipsdist = True
50+
usedevelop = True
51+
passenv = *
52+
deps =
53+
colorama
54+
gitpython
55+
towncrier
56+
commands = python scripts/release.py {posargs}

0 commit comments

Comments
 (0)