Skip to content

Commit e630aea

Browse files
GH Action for creating releases (#503)
* adds tools for changelog and release management * use github token * github_token is used by default, duh * also check for already-existing higher versions * workflow * fixes * pr feedback * Update make_changelog.py Co-authored-by: Philipp Otto <[email protected]> Co-authored-by: Philipp Otto <[email protected]>
1 parent e72950c commit e630aea

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed

.github/workflows/release.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Automatic release
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: "Version of the new release (e.g. `0.0.1`)"
8+
required: true
9+
10+
jobs:
11+
release:
12+
runs-on: ubuntu-latest
13+
env:
14+
VERSION: ${{ github.event.inputs.version }}
15+
steps:
16+
- uses: actions/checkout@v2
17+
with:
18+
fetch-depth: 0
19+
ref: auto-changelogs
20+
21+
- uses: actions/setup-python@v1
22+
with:
23+
python-version: "3.8"
24+
architecture: 'x64'
25+
26+
- name: Setup git config
27+
run: |
28+
git config user.name "Automatic release"
29+
git config user.email "<>"
30+
31+
- name: Check whether tag already exists
32+
run: |
33+
if git show-ref --tags "v${VERSION}" --quiet; then
34+
echo "Version $VERSION already exists. Stopping."
35+
exit 1
36+
fi
37+
38+
- name: Make and push release
39+
run: |
40+
./make_release.sh ${VERSION}
41+
git add */Changelog.md
42+
43+
git commit -m "Release for v${VERSION}"
44+
git tag v${VERSION}
45+
46+
git push origin master
47+
git push --tags

check_version.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from subprocess import run
2+
from re import match
3+
import sys
4+
5+
this_version = tuple(int(a) for a in sys.argv[1].split("."))
6+
7+
max_git_tag = max(
8+
[
9+
tuple(int(a) for a in line[1:].split("."))
10+
for line in run(["git", "tag"], capture_output=True)
11+
.stdout.decode("utf-8")
12+
.split("\n")
13+
if match("^v\d+\.\d+.\d+$", line)
14+
]
15+
)
16+
17+
if this_version > max_git_tag:
18+
sys.exit(0)
19+
else:
20+
sys.exit(1)

make_changelog.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import datetime
2+
import re
3+
import sys
4+
5+
# Read existing changelog
6+
with open("Changelog.md") as f:
7+
changelog_lines = list(s.rstrip() for s in f)
8+
9+
# Determine new version
10+
this_version = sys.argv[1]
11+
today = datetime.date.today()
12+
today_str = f"{today.strftime('%Y')}-{today.strftime('%m')}-{today.strftime('%d')}"
13+
14+
# Determine last version
15+
matches = re.finditer(r"^## \[v?(.*)\].*$", "\n".join(changelog_lines), re.MULTILINE)
16+
last_version = next(matches)[1]
17+
18+
# Stop the script if new version is already the latest
19+
if last_version == this_version:
20+
print(f"Version {this_version} is already up-to-date.")
21+
sys.exit(0)
22+
23+
# Find line with "## Unreleased" heading
24+
unreleased_idx = next(
25+
i for i, line in enumerate(changelog_lines) if line.startswith("## Unreleased")
26+
)
27+
28+
# Find line with the last release (i.e. "## x.y.z" heading)
29+
last_release_idx = next(
30+
i
31+
for i, line in enumerate(changelog_lines)
32+
if line.startswith("## ") and i > unreleased_idx
33+
)
34+
35+
# Clean up unreleased notes (i.e. remove empty sections)
36+
released_notes = "\n".join(changelog_lines[(unreleased_idx + 2) : last_release_idx])
37+
38+
release_section_fragments = re.split("\n### (.*)\n", released_notes, re.MULTILINE)
39+
release_notes_intro = release_section_fragments[0]
40+
release_sections = list(
41+
zip(release_section_fragments[1::2], release_section_fragments[2::2])
42+
)
43+
nonempty_release_sections = [
44+
(section, content) for section, content in release_sections if content.strip() != ""
45+
]
46+
47+
cleaned_release_notes = (
48+
"\n".join(
49+
[release_notes_intro]
50+
+ [
51+
f"### {section}\n{content}"
52+
for section, content in nonempty_release_sections
53+
]
54+
)
55+
).split("\n")
56+
57+
# Update changelog
58+
lines_to_insert = [
59+
"## Unreleased",
60+
f"[Commits](https://github.com/scalableminds/webknossos-libs/compare/v{this_version}...HEAD)",
61+
"",
62+
"### Breaking Changes",
63+
"",
64+
"### Added",
65+
"",
66+
"### Changed",
67+
"",
68+
"### Fixed",
69+
"",
70+
"",
71+
f"## [{this_version}](https://github.com/scalableminds/webknossos-libs/releases/tag/v{this_version}) - {today_str}",
72+
f"[Commits](https://github.com/scalableminds/webknossos-libs/compare/v{last_version}...v{this_version})",
73+
]
74+
changelog_lines = (
75+
changelog_lines[:unreleased_idx]
76+
+ lines_to_insert
77+
+ cleaned_release_notes
78+
+ [""]
79+
+ changelog_lines[(last_release_idx):]
80+
+ [""]
81+
)
82+
83+
with open("Changelog.md", "wt") as f:
84+
f.write("\n".join(changelog_lines))

make_release.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env bash
2+
set -eEuo pipefail
3+
set +x
4+
5+
if [[ $# -eq 0 ]] ; then
6+
echo "Please supply a 'version' as argument."
7+
exit 1
8+
fi
9+
10+
PKG_VERSION="$1"
11+
12+
if ! python check_version.py ${PKG_VERSION}; then
13+
echo "A higher version is already present."
14+
exit 1
15+
fi
16+
17+
for PKG in */pyproject.toml; do
18+
PKG="$(dirname "$PKG")"
19+
if [[ "$PKG" == "docs" ]]; then
20+
echo Skipping "$PKG"
21+
continue
22+
fi
23+
echo "Creating release for $PKG"
24+
25+
pushd "$PKG" > /dev/null
26+
27+
python ../make_changelog.py "$PKG_VERSION"
28+
29+
popd > /dev/null
30+
done

0 commit comments

Comments
 (0)