Skip to content

Commit f6d3257

Browse files
committed
Merge PR 768: chore: collect changelog entries with towncrier
#768 After 3.5.0, every change adds a changelog fragment to changes/, and towncrier build merges them into CHANGELOG.md at release time, so parallel pull requests no longer conflict there. Commits: - b86aa0f chore: collect changelog entries with towncrier - ded8ef9 docs: list unreviewed master commits as a mistake Assisted-By: Claude Opus 5.5 (Claude Code) Reviewed-by: Tobias Brox <tobias@redpill-linpro.com>
2 parents 7563559 + ded8ef9 commit f6d3257

8 files changed

Lines changed: 126 additions & 23 deletions

File tree

‎.lycheeignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,6 @@ https://dav\.qq\.com/.*
6464
# pushes the code before the tag, so this view 404s until `v3.3.0` is pushed -
6565
# delete this entry once it is.
6666
https://github\.com/python-caldav/caldav/compare/v3\.2\.1\.\.\.v3\.3\.0
67+
68+
# The issue_format template in pyproject.toml (towncrier)
69+
https://github\.com/python-caldav/caldav/issues/(%7B|\{)issue(%7D|\})

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ Changelogs prior to v3.0 are pruned, but are available in the v3.1 release
2222

2323
This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), though for pre-releases PEP 440 takes precedence.
2424

25+
After 3.5.0, changes since the last release are kept as fragments in [changes/](changes/), and merged in here at release time.
26+
27+
<!-- towncrier release notes start -->
28+
2529
## [3.5.0] - 2026-10-15
2630

2731
Yet another release primarily focusing on bugfixes and compatibility. iCloud has now been tested and got its compatibility profile in `compatibility_hints.py`. Test runs towards GMX, Posteo and Purelymail have been skipped for the last few releases; with 3.5.0 the tests pass towards those. More work on getting synchronization right, and on avoiding overwriting changes when multiple clients update the same object.

‎CONTRIBUTING.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ Starting from v3.0.1, we'll stick to https://www.conventionalcommits.org/en/v1.0
99
The types used should (as for now) be one of:
1010

1111
* "revert" - a clean revert of a previous commit (should be used very infrequently on the master branch)
12-
* "feat" - a new feature added to the codebase/API. The commit should include test code, documentation and a CHANGELOG entry unless there are good reasons for procrastinating it. "feat" should not be used for new features that only affects the test framework, or changes only affectnig the documentation etc.
13-
* "fix" - a bugfix. Again, documentation and CHANGELOG-entry should be included in the commit (notable exception: if a bug was introduced after the previous release and fixed before the next release, it does not need to be mentioned in the CHANGELOG). If the only "fix" is that some typo is fixed in some existing documentation, then we should use "docs" instead.
12+
* "feat" - a new feature added to the codebase/API. The commit should include test code, documentation and a changelog fragment (see below) unless there are good reasons for procrastinating it. "feat" should not be used for new features that only affects the test framework, or changes only affecting the documentation etc.
13+
* "fix" - a bugfix. Again, documentation and a changelog fragment should be included in the commit (notable exception: if a bug was introduced after the previous release and fixed before the next release, it does not need to be mentioned in the CHANGELOG). If the only "fix" is that some typo is fixed in some existing documentation, then we should use "docs" instead.
1414
* "perf" - a code change in the codebase that is neither a bugfix or a feature, but intends to improve performance.
1515
* "refactor" - a code change in the codebase that is neither a bugfix or a feature, but makes the code more readable, shorter, better or more maintainable.
1616
* "test" - fixes, additions or improvements that only affects the test code or the test framework. The commit may include documentation.
17-
* "docs" - changes that *only* is done to the documentation, documentation framework - this includes minor typo fixes as well as new documentation, and it includes both the user documentation under `docs/source`, other documentation files (including CHANGELOG) as well as inline comments and docstrings in the code itself.
17+
* "docs" - changes that *only* is done to the documentation, documentation framework - this includes minor typo fixes as well as new documentation, and it includes both the user documentation under `docs/source`, other documentation files (including CHANGELOG and changelog fragments) as well as inline comments and docstrings in the code itself.
1818
* "chore" - if nothing of the above fits
1919

2020
The `compatibility_hints.py` has been moved from the test directory to the codebase not so very long ago. Some special rules here:
@@ -72,7 +72,7 @@ Consider this procedures to be a more of a guideline than a rigid procedure. Us
7272

7373
* Consider to write some lines in the documentation, changelog and/or examples covering your change
7474

75-
* Add an entry in the `CHANGELOG.md` file.
75+
* Add a changelog fragment: a file `changes/<issue>.<type>.md`, e.g. `changes/740.fixed.md`, holding the text of the entry (without an issue: `changes/+<slug>.<type>.md`). Do not edit `CHANGELOG.md` itself - the fragments are collected into it at release time, so that parallel pull requests do not conflict there. [changes/README.md](changes/README.md) has the details.
7676

7777
* Create a pull request
7878

‎changes/README.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Changelog fragments
2+
3+
Changes since the last release are not written into `CHANGELOG.md` directly. Each change adds a small file - a "fragment" - to this directory instead, and at release time [towncrier](https://towncrier.readthedocs.io/) collects them into `CHANGELOG.md`. Separate files mean that parallel pull requests no longer conflict in the CHANGELOG.
4+
5+
## File naming
6+
7+
`<issue>.<type>.md`, e.g. `740.fixed.md` for a fix of https://github.com/python-caldav/caldav/issues/740 - the URL is appended to the entry automatically. A pull request number works too. If two changes of the same type belong to one issue, add a counter: `740.fixed.1.md`, `740.fixed.2.md` (`towncrier create` picks one when the name is taken).
8+
9+
For a change without an issue, or when the entry cites its URLs inline (several issues, a pull request and an issue, an upstream bug report ...), use `+<slug>.<type>.md`, e.g. `+async-xml-parsing.fixed.md`. Nothing is appended to such an entry.
10+
11+
The types, in the order they appear in the CHANGELOG:
12+
13+
| type | section |
14+
|--------------|-------------------------|
15+
| `breaking` | Breaking changes |
16+
| `added` | Added |
17+
| `changed` | Changed |
18+
| `deprecated` | Deprecated |
19+
| `removed` | Removed |
20+
| `fixed` | Fixed |
21+
| `security` | Security |
22+
| `tests` | Tests and documentation |
23+
24+
Use `tests` only for changes a user or packager notices (a new test option, a test dependency, docs that moved); a pull request that only adds or changes tests or docs needs no fragment.
25+
26+
## Content
27+
28+
Markdown, written as the text of one bullet point, without the leading `* `. Describe the change as seen by a user of the library: what was wrong or missing, and what it does now. Several paragraphs or a nested list are fine.
29+
30+
The rules in [CONTRIBUTING.md](../CONTRIBUTING.md) for what needs an entry are unchanged - a bug introduced and fixed between two releases needs none, so delete or edit the fragment of the change that introduced it instead.
31+
32+
`towncrier create 740.fixed.md --edit` creates a fragment, and `towncrier build --draft --version next` previews the next release section (`pip install towncrier`).

‎changes/template.md.jinja‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{#- Renders entries the way CHANGELOG.md has always had them: "* " bullets,
2+
with the issue URLs appended after two spaces (or as a paragraph of their
3+
own when the entry spans several lines), separated by " - ". -#}
4+
{%- set nl = "\n" -%}
5+
{{- "## [" ~ versiondata.version ~ "] - " ~ versiondata.date ~ nl }}
6+
{%- for section, _ in sections.items() %}
7+
{%- for category, val in definitions.items() if category in sections[section] %}
8+
{{- nl ~ "### " ~ definitions[category]['name'] ~ nl ~ nl }}
9+
{%- for text, values in sections[section][category].items() %}
10+
{{- "* " ~ text }}
11+
{%- if values %}
12+
{{- (nl ~ nl ~ " " if nl in text else " ") ~ values | join(" - ") }}
13+
{%- endif %}
14+
{{- nl }}
15+
{%- endfor %}
16+
{%- endfor %}
17+
{%- endfor %}
18+
{{- nl -}}

‎docs/design/RELEASE-HOWTO.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ I have no clue on the proper procedures for doing releases, and I keep on doing
66

77
(And no - we cannot do auto-releases of CalDAV. Every release has to be well-tested, some of the tests needs my private configuration with passwords and usernames for various caldav servers, and some of the docker containers included are sluggish, full test runs takes very long time).
88

9+
## One-off: after the switch to towncrier
10+
11+
3.5.0 was the last release whose `CHANGELOG.md` section was written by hand. Branches and pull requests still carrying an entry in `CHANGELOG.md` need it moved into a fragment in `changes/` before they are merged. Delete this section once none are left.
12+
913
## Checklist
1014

11-
* Go through changes since last release and compare it with the `CHANGELOG.md`. Any change should be logged.
15+
* Go through changes since last release and compare it with the changelog fragments in `changes/` (`towncrier build --draft --version X` shows them as they will end up in the `CHANGELOG.md`, see `changes/README.md`). Any change should be logged.
1216
* Run tests towards as many servers as possible
1317
* Use the `PYTHON_CALDAV_DEBUGMODE=DEBUG_PDB` environment variable (or simply pass `--pdb` to pytest, which sets this automatically)! Should do some research if we hit any "soft asserts" or "weirdness".
1418
* Do research on breakages. If the test breaks also for the previous release of the caldav library, then it's likely to be due to some regression on the server side. For patch-level releases such breakages may be allowed, for minor-level releases we should try to work around problems
@@ -17,11 +21,12 @@ I have no clue on the proper procedures for doing releases, and I keep on doing
1721
* Look through github pull requests and see if there is anything that ought to be included in the release
1822
* For minor and major releases, look through the github issues, anything urgent there that should be fixed prior to doing a new release?
1923
* Any changes done, go back to the start of this list
20-
* Write up some release notes. (I typically keep a short summary of the changes in the CHANGELOG, and use that as the release notes).
24+
* Write up some release notes. (I typically keep a short summary of the changes in the CHANGELOG, and use that as the release notes - see the `towncrier build` step below).
2125
* Verify that we're on the right branch - `git checkout master`. (`master` may not always be right - sometimes we may want to use a dedicated branch connected to the release-series, i.e. `v1.3`)
2226
* TODO - document needs to be updated - as the test runs on github now takes significant amounts of time, it's important to push the code first and wait for quite a while before tagging and pushing the tag.
2327
* Set the variable `VERSION=2.2.0`
24-
* Commit the changes (typically `CHANGELOG.md`, perhaps documentation): `git commit -am "docs: preparing for v${VERSION}"`
28+
* Collect the changelog fragments: `towncrier build --yes --version ${VERSION}` (`pip install towncrier`; without `--yes` it asks before removing the fragments). It writes a `## [${VERSION}] - <date>` section into `CHANGELOG.md` at the `<!-- towncrier release notes start -->` marker and `git rm`s the fragments. The date is the day of the build - fix it if the tag comes later. The result is plain markdown - now edit it by hand: towncrier sorts the entries by issue number, so reorder them by importance, add the summary on top, and add the `[${VERSION}]: https://github.com/python-caldav/caldav/compare/vPREVIOUS...v${VERSION}` link at the bottom of the file.
29+
* Commit the changes (typically `CHANGELOG.md` and the removed fragments, perhaps documentation): `git commit -am "docs: preparing for v${VERSION}"`
2530
* Create a tag: `git tag -as v${VERSION}` - use the release notes in the tag message. Don't push it yet.
2631
* Make a clone: `cd ~ ; rm -rf caldav-release ; git clone caldav/ caldav-release ; cd caldav-release ; git checkout v${VERSION}`
2732
* Run tests: `pytest --pdb ; tox -e style`.
@@ -56,4 +61,5 @@ This is most likely not complete, but should explain some of the "silly" steps a
5661
* Forgetting to push to pypi, or pushing something else than the tagged revision to pypi
5762
* Pushing out junk files in the pypi-release (i.e. .pyc-files, log files, temp files, `tests/conf_private.py`, `tests/caldav_test_servers.yaml`, an entire `venv/`, etc). `tox -e package` now catches this - see the build step above
5863
* Not adding the release to the "github releases" (I don't care much about this feature, but apparently some people check there to find the latest release version)
59-
* Not committing after doing a s/unreleased/$VERSION/ on the CHANGELOG
64+
* Not committing after running `towncrier build` (or, up to and including 3.5.0, after doing a s/unreleased/$VERSION/ on the CHANGELOG)
65+
* "Playing ball" with Claude on the CHANGELOG and committing the changes directly to master before a release, not going through the regular QA - and suddenly the master branch has 6-7 commits with various irrelevant prompts attached.

‎docs/design/TODO.md‎

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,9 @@ This violates CalDAV RFC expectations and is a Nextcloud bug. Restarting the Do
2424
3. Consider adding server quirk detection in caldav_server_tester
2525
4. Document workaround: avoid UID reuse with Nextcloud, or restart container between test runs
2626

27-
## Switch CHANGELOG to towncrier
27+
## `towncrier check` in CI
2828

29-
**When**: right after the next release (3.5), so the Unreleased section is empty.
30-
31-
Parallel PRs (third-party and own) keep conflicting in `CHANGELOG.md`. Use towncrier fragments (`changes/<issue>.<type>.md`, `changes/+slug.<type>.md` without an issue) instead. Tested OK with towncrier 26.9; nested lists in fragments render fine.
32-
33-
Requirements kept: `towncrier build --version X` writes plain markdown, so the CHANGELOG can still be hand-edited and an executive summary added before the release commit.
34-
35-
Steps:
36-
1. `[tool.towncrier]` in `pyproject.toml`: `directory = "changes"`, `start_string = "<!-- towncrier release notes start -->\n"` (marker after the "Meta" section), `title_format = "## [{version}] - {project_date}"`, `issue_format` pointing at GitHub issues, and one `[[tool.towncrier.type]]` per Keep-a-Changelog section (Breaking changes, Added, Changed, Deprecated, Removed, Fixed, Security).
37-
2. `changes/README.md` explaining file naming.
38-
3. Update CONTRIBUTING.md (commit types section and "Add an entry in the CHANGELOG.md").
39-
4. Optional: `towncrier check` in CI, with a `no-changelog` label for test/docs-only PRs.
40-
5. Release checklist: `towncrier build --version X` → reorder/hand-edit → add summary → commit → tag.
41-
42-
Known downsides: no Unreleased section visible on master (consider `sphinxcontrib-towncrier`), entries sorted by issue number rather than importance, `(issue URL)` appended to entries — use `+slug` names where URLs are cited inline.
29+
towncrier was set up after 3.5.0 (`changes/README.md`). Not done: a CI job running `towncrier check` on pull requests (with `--compare-with origin/master`, as towncrier defaults to `origin/main`), so that a missing changelog fragment is caught. It needs a way to skip test/docs-only pull requests, which need no entry (e.g. a `no-changelog` label), and should not be enabled before the open pull requests have been moved to fragments. Note that `towncrier check` also passes when `CHANGELOG.md` itself was edited, so it does not catch a pull request that still edits it directly. Also consider `sphinxcontrib-towncrier`, as from 3.5.0 on there is no "Unreleased" section in `CHANGELOG.md`.
4330

4431
## An error response with a non-XML body labelled as XML
4532

‎pyproject.toml‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,56 @@ filterwarnings = [
219219
# Zimbra test server uses HTTPS with self-signed cert (ssl_verify_cert=False)
220220
"ignore:Unverified HTTPS request:urllib3_future.exceptions.InsecureRequestWarning",
221221
]
222+
223+
## Changelog fragments: changes/README.md explains the file naming, and
224+
## docs/design/RELEASE-HOWTO.md how they are merged into CHANGELOG.md.
225+
[tool.towncrier]
226+
directory = "changes"
227+
filename = "CHANGELOG.md"
228+
template = "changes/template.md.jinja"
229+
start_string = "<!-- towncrier release notes start -->\n"
230+
## The title, "## [{version}] - {date}", is rendered by the template
231+
title_format = false
232+
issue_format = "https://github.com/python-caldav/caldav/issues/{issue}"
233+
## editor leftovers too, as an unknown file name aborts the build
234+
ignore = ["README.md", "template.md.jinja", "*~", ".#*", "#*#"]
235+
236+
[[tool.towncrier.type]]
237+
directory = "breaking"
238+
name = "Breaking changes"
239+
showcontent = true
240+
241+
[[tool.towncrier.type]]
242+
directory = "added"
243+
name = "Added"
244+
showcontent = true
245+
246+
[[tool.towncrier.type]]
247+
directory = "changed"
248+
name = "Changed"
249+
showcontent = true
250+
251+
[[tool.towncrier.type]]
252+
directory = "deprecated"
253+
name = "Deprecated"
254+
showcontent = true
255+
256+
[[tool.towncrier.type]]
257+
directory = "removed"
258+
name = "Removed"
259+
showcontent = true
260+
261+
[[tool.towncrier.type]]
262+
directory = "fixed"
263+
name = "Fixed"
264+
showcontent = true
265+
266+
[[tool.towncrier.type]]
267+
directory = "security"
268+
name = "Security"
269+
showcontent = true
270+
271+
[[tool.towncrier.type]]
272+
directory = "tests"
273+
name = "Tests and documentation"
274+
showcontent = true

0 commit comments

Comments
 (0)