Skip to content
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

DEV: Automatically create release message / tag message #2127

Merged
merged 1 commit into from
Aug 28, 2023
Merged
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
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ ignore = E203,E501,E741,W503,W604,N817,N814,VNE001,VNE002,VNE003,N802,SIM105,P10
exclude = build,sample-files,dist,.benchmarks,.git,.github,.mypy_cache,.pytest_cache,.tox
per-file-ignores =
tests/*: ASS001,PT011,B011,T001,T201
make_changelog.py:T001,T201
make_release.py:T001,T201
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ docs/meta/CONTRIBUTORS.md
extracted-images/

RELEASE_COMMIT_MSG.md
RELEASE_TAG_MSG.md
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ maint:
pyenv local 3.7.9
pip-compile -U requirements/docs.in

changelog:
python make_changelog.py
release:
python make_release.py
git commit -eF RELEASE_COMMIT_MSG.md

upload:
make clean
Expand Down
232 changes: 116 additions & 116 deletions docs/_static/releasing.drawio

Large diffs are not rendered by default.

Binary file modified docs/_static/releasing.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion docs/dev/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ you `git commit`.

Having a clean commit message helps people to quickly understand what the commit
was about, without actually looking at the changes. The first line of the
commit message is used to [auto-generate the CHANGELOG](https://github.com/py-pdf/pypdf/blob/main/make_changelog.py). For this reason, the format should be:
commit message is used to [auto-generate the CHANGELOG](https://github.com/py-pdf/pypdf/blob/main/make_release.py).
For this reason, the format should be:

```
PREFIX: DESCRIPTION
Expand Down
22 changes: 5 additions & 17 deletions docs/dev/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,16 @@ release.

The release contains the following steps:

1. Create the changelog with `python make_changelog.py` and adjust the `_version.py`
2. Create a release commit
3. Tag that commit
4. Push both
1. Update the CHANGELOG.md and the _version.py via `python make_release.py`.
This also prepares the release commit message
2. Create a release commit: `git commit -eF RELEASE_COMMIT_MSG.md`.
3. Tag that commit: `git tag -s {{version}} -eF RELEASE_TAG_MSG.md`.
4. Push both: `git push && git push --tags`
5. CI now builds a source and a wheels package which it pushes to PyPI. It also
creates a GitHub release.

![](../_static/releasing.drawio.png)

### The Release Commit

The release commit is used to create the GitHub release page. The structure of
it should be:

```
REL: {{ version }}

## What's new

{{ CHANGELOG }}
```

### The Release Tag

* Use the release version as the tag name. No need for a leading "v".
Expand Down
68 changes: 68 additions & 0 deletions make_changelog.py → make_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from datetime import datetime, timezone
from typing import List

from rich.prompt import Prompt


@dataclass(frozen=True)
class Change:
Expand All @@ -30,13 +32,17 @@ def main(changelog_path: str) -> None:
return

new_version = version_bump(git_tag)
new_version = get_version_interactive(new_version, changes)
adjust_version_py(new_version)

today = datetime.now(tz=timezone.utc)
header = f"## Version {new_version}, {today:%Y-%m-%d}\n"
url = f"https://github.com/py-pdf/pypdf/compare/{git_tag}...{new_version}"
trailer = f"\n[Full Changelog]({url})\n\n"
new_entry = header + changes + trailer
print(new_entry)
write_commit_msg_file(new_version, changes + trailer)
write_release_msg_file(new_version, changes + trailer, today)

# Make the script idempotent by checking if the new entry is already in the changelog
if new_entry in changelog:
Expand All @@ -45,6 +51,52 @@ def main(changelog_path: str) -> None:

new_changelog = "# CHANGELOG\n\n" + new_entry + strip_header(changelog)
write_changelog(new_changelog, changelog_path)
print_instructions(new_version)


def print_instructions(new_version: str) -> None:
"""Print release instructions."""
print("=" * 80)
print(f"☑ _version.py was adjusted to '{new_version}'")
print("☑ CHANGELOG.md was adjusted")
print("")
print("Now run:")
print(" git commit -eF RELEASE_COMMIT_MSG.md")
print(f" git tag -s {new_version} -eF RELEASE_TAG_MSG.md")
print(" git push")
print(" git push --tags")


def adjust_version_py(version: str) -> None:
"""Adjust the __version__ string."""
with open("pypdf/_version.py", "w") as fp:
fp.write(f'__version__ = "{version}"\n')


def get_version_interactive(new_version: str, changes: str) -> str:
"""Get the new __version__ interactively."""
print("The changes are:")
print(changes)
orig = new_version
new_version = Prompt.ask("New semantic version", default=orig)
while not is_semantic_version(new_version):
new_version = Prompt.ask(
"That was not a semantic version. Please enter a semantic version",
default=orig,
)
return new_version


def is_semantic_version(version: str) -> bool:
"""Check if the given version is a semantic version."""
# It's not so important to cover the edge-cases like pre-releases
# This is meant for pypdf only and we don't make pre-releases
if version.count(".") != 2:
return False
try:
return bool([int(part) for part in version.split(".")])
except Exception:
return False


def write_commit_msg_file(new_version: str, commit_changes: str) -> None:
Expand All @@ -61,6 +113,22 @@ def write_commit_msg_file(new_version: str, commit_changes: str) -> None:
fp.write(commit_changes)


def write_release_msg_file(
new_version: str, commit_changes: str, today: datetime
) -> None:
"""
Write a file that can be used as a git tag message.

Like this:

git tag -eF RELEASE_TAG_MSG.md && git push
"""
with open("RELEASE_TAG_MSG.md", "w") as fp:
fp.write(f"Version {new_version}, {today:%Y-%m-%d}\n\n")
fp.write("## What's new\n")
fp.write(commit_changes)


def strip_header(md: str) -> str:
"""Remove the 'CHANGELOG' header."""
return md.lstrip("# CHANGELOG").lstrip() # noqa
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ tests_dir = "tests/"
package = "./pypdf"

[tool.flit.sdist]
exclude = [".github/*", "docs/*", "resources/*", "sample-files/*", "sample-files/.github/*", "sample-files/.gitignore", "sample-files/.pre-commit-config.yaml", "requirements/*", "tests/*", ".flake8", ".gitignore", ".gitmodules", ".pylintrc", "tox.ini", "make_changelog.py", "mutmut-test.sh", ".pre-commit-config.yaml", ".gitblame-ignore-revs", "Makefile", "mutmut_config.py"]
exclude = [".github/*", "docs/*", "resources/*", "sample-files/*", "sample-files/.github/*", "sample-files/.gitignore", "sample-files/.pre-commit-config.yaml", "requirements/*", "tests/*", ".flake8", ".gitignore", ".gitmodules", ".pylintrc", "tox.ini", "make_release.py", "mutmut-test.sh", ".pre-commit-config.yaml", ".gitblame-ignore-revs", "Makefile", "mutmut_config.py"]

[tool.pytest.ini_options]
addopts = "--disable-socket"
Expand Down Expand Up @@ -201,7 +201,7 @@ max-complexity = 54 # Recommended: 10
"_writer.py" = ["S324"]
"docs/conf.py" = ["PTH", "INP001"]
"json_consistency.py" = ["T201"]
"make_changelog.py" = ["T201", "S603", "S607"]
"make_release.py" = ["T201", "S603", "S607"]
"pypdf/*" = ["N802", "N803"] # We first need to deprecate old stuff:
"sample-files/*" = ["D100", "INP001"]
"tests/*" = ["S101", "ANN001", "ANN201","D104", "S105", "S106", "D103", "B018", "B017"]
Expand Down