Skip to content

Commit 5dd94ff

Browse files
committed
simplify release generator
1 parent 7f1a843 commit 5dd94ff

File tree

3 files changed

+13
-343
lines changed

3 files changed

+13
-343
lines changed

tools/release/create_release.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
11
#!/usr/bin/env python3
22

3-
import os
3+
from os import environ, path
44
from github import Github
5-
import lib.release as release
5+
from lib.release import create_release
66

7-
WORK_DIR = os.path.dirname(os.path.abspath(__file__)).replace('/tools/release', '')
7+
WORK_DIR = path.dirname(path.abspath(__file__)).replace('/tools/release', '')
88

9-
NEXT_TAG = os.environ['NEXT_RELEASE_TAG']
10-
REPO_NAME = os.environ['GITHUB_REPOSITORY']
11-
TOKEN = os.environ['GITHUB_TOKEN']
9+
NEXT_TAG = environ.get('NEXT_RELEASE_TAG', None)
10+
REPO_NAME = environ.get('GITHUB_REPOSITORY', None)
11+
TOKEN = environ.get('GITHUB_TOKEN', None)
1212
if not NEXT_TAG or not REPO_NAME or not TOKEN:
1313
raise Exception('Bad environment variables. Invalid GITHUB_REPOSITORY, GITHUB_TOKEN or NEXT_RELEASE_TAG')
1414

1515
g = Github(TOKEN)
16-
repo = g.get_repo(REPO_NAME)
16+
repository = g.get_repo(REPO_NAME)
1717

18-
release_notes = release.contruct_release_notes(repo, NEXT_TAG)
19-
20-
release.create_release(repo, NEXT_TAG, release_notes)
21-
22-
release = repo.get_release(NEXT_TAG)
18+
release = create_release(repository, NEXT_TAG)
2319
release.upload_asset('singleheader/ada.cpp')
2420
release.upload_asset('singleheader/ada.h')
2521
release.upload_asset('singleheader/ada_c.h')

tools/release/lib/release.py

Lines changed: 5 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -1,201 +1,21 @@
11
#!/usr/bin/env python3
22

33
import re
4-
from typing import Optional, List, Set, Union, Type
5-
from github.PullRequest import PullRequest
6-
from github.GitRelease import GitRelease
7-
from github.Repository import Repository
4+
from github import Repository, GitRelease
85

96

107
def is_valid_tag(tag: str) -> bool:
118
tag_regex = r'^v\d+\.\d+\.\d+$'
129
return bool(re.match(tag_regex, tag))
1310

1411

15-
def create_release(repository: Repository, tag: str, notes: str) -> Union[None, Type[Exception]]:
12+
def create_release(repository: Repository, tag: str) -> GitRelease:
1613
if not is_valid_tag(tag):
1714
raise Exception(f'Invalid tag: {tag}')
1815

1916
try:
20-
repository.create_git_release(tag=tag, name=tag, message=notes, draft=False, prerelease=False)
21-
17+
return repository.create_git_release(
18+
tag=tag, name=tag, draft=False, prerelease=False, generate_release_notes=True
19+
)
2220
except Exception as exp:
2321
raise Exception(f'create_release: Error creating release/tag {tag}: {exp!s}') from exp
24-
25-
26-
def get_sorted_merged_pulls(pulls: List[PullRequest], last_release: Optional[GitRelease]) -> List[PullRequest]:
27-
# Get merged pulls after last release
28-
if not last_release:
29-
return sorted(
30-
(
31-
pull
32-
for pull in pulls
33-
if pull.merged
34-
and pull.base.ref == 'main'
35-
and not pull.title.startswith('chore: release')
36-
and not pull.user.login.startswith('github-actions')
37-
),
38-
key=lambda pull: pull.merged_at,
39-
)
40-
41-
return sorted(
42-
(
43-
pull
44-
for pull in pulls
45-
if pull.merged
46-
and pull.base.ref == 'main'
47-
and (pull.merged_at > last_release.created_at)
48-
and not pull.title.startswith('chore: release')
49-
and not pull.user.login.startswith('github-actions')
50-
),
51-
key=lambda pull: pull.merged_at,
52-
)
53-
54-
55-
def get_pr_contributors(pull_request: PullRequest) -> List[str]:
56-
contributors = set()
57-
for commit in pull_request.get_commits():
58-
commit_message = commit.commit.message
59-
if commit_message.startswith('Co-authored-by:'):
60-
coauthor = commit_message.split('<')[0].split(':')[-1].strip()
61-
contributors.add(coauthor)
62-
else:
63-
author = commit.author
64-
if author:
65-
contributors.add(author.login)
66-
return sorted(list(contributors), key=str.lower)
67-
68-
69-
def get_old_contributors(pulls: List[PullRequest], last_release: Optional[GitRelease]) -> Set[str]:
70-
contributors = set()
71-
if last_release:
72-
merged_pulls = [pull for pull in pulls if pull.merged and pull.merged_at <= last_release.created_at]
73-
74-
for pull in merged_pulls:
75-
pr_contributors = get_pr_contributors(pull)
76-
for contributor in pr_contributors:
77-
contributors.add(contributor)
78-
79-
return contributors
80-
81-
82-
def get_new_contributors(old_contributors: List[str], merged_pulls: List[PullRequest]) -> List[str]:
83-
new_contributors = set()
84-
for pull in merged_pulls:
85-
pr_contributors = get_pr_contributors(pull)
86-
for contributor in pr_contributors:
87-
if contributor not in old_contributors:
88-
new_contributors.add(contributor)
89-
90-
return sorted(list(new_contributors), key=str.lower)
91-
92-
93-
def get_last_release(releases: List[GitRelease]) -> Optional[GitRelease]:
94-
sorted_releases = sorted(releases, key=lambda r: r.created_at, reverse=True)
95-
96-
if sorted_releases:
97-
return sorted_releases[0]
98-
99-
return None
100-
101-
102-
def multiple_contributors_mention_md(contributors: List[str]) -> str:
103-
contrib_by = ''
104-
if len(contributors) <= 1:
105-
for contrib in contributors:
106-
contrib_by += f'@{contrib}'
107-
else:
108-
for contrib in contributors:
109-
contrib_by += f'@{contrib}, '
110-
111-
contrib_by = contrib_by[:-2]
112-
last_comma = contrib_by.rfind(', ')
113-
contrib_by = contrib_by[:last_comma].strip() + ' and ' + contrib_by[last_comma + 1 :].strip()
114-
return contrib_by
115-
116-
117-
def whats_changed_md(repo_full_name: str, merged_pulls: List[PullRequest]) -> List[str]:
118-
whats_changed = []
119-
for pull in merged_pulls:
120-
contributors = get_pr_contributors(pull)
121-
contrib_by = multiple_contributors_mention_md(contributors)
122-
123-
whats_changed.append(
124-
f'* {pull.title} by {contrib_by} in https://github.com/{repo_full_name}/pull/{pull.number}'
125-
)
126-
127-
return whats_changed
128-
129-
130-
def get_first_contribution(merged_pulls: List[str], contributor: str) -> Optional[PullRequest]:
131-
for pull in merged_pulls:
132-
contrubutors = get_pr_contributors(pull)
133-
if contributor in contrubutors:
134-
return pull
135-
136-
# ? unreachable
137-
return None
138-
139-
140-
def new_contributors_md(repo_full_name: str, merged_pulls: List[PullRequest], new_contributors: List[str]) -> List[str]:
141-
contributors_by_pr = {}
142-
contributors_md = []
143-
for contributor in new_contributors:
144-
first_contrib = get_first_contribution(merged_pulls, contributor)
145-
146-
if not first_contrib:
147-
continue
148-
149-
if first_contrib.number not in contributors_by_pr.keys():
150-
contributors_by_pr[first_contrib.number] = [contributor]
151-
else:
152-
contributors_by_pr[first_contrib.number] += [contributor]
153-
154-
contributors_by_pr = dict(sorted(contributors_by_pr.items()))
155-
for pr_number, contributors in contributors_by_pr.items():
156-
contributors.sort(key=str.lower)
157-
contrib_by = multiple_contributors_mention_md(contributors)
158-
159-
contributors_md.append(
160-
f'* {contrib_by} made their first contribution in https://github.com/{repo_full_name}/pull/{pr_number}'
161-
)
162-
163-
return contributors_md
164-
165-
166-
def full_changelog_md(repository_name: str, last_tag_name: str, next_tag_name: str) -> Optional[str]:
167-
if not last_tag_name:
168-
return None
169-
return f'**Full Changelog**: https://github.com/{repository_name}/compare/{last_tag_name}...{next_tag_name}'
170-
171-
172-
def contruct_release_notes(repository: Repository, next_tag_name: str) -> str:
173-
repo_name = repository.full_name
174-
last_release = get_last_release(repository.get_releases())
175-
all_pulls = repository.get_pulls(state='closed')
176-
177-
sorted_merged_pulls = get_sorted_merged_pulls(all_pulls, last_release)
178-
old_contributors = get_old_contributors(all_pulls, last_release)
179-
new_contributors = get_new_contributors(old_contributors, sorted_merged_pulls)
180-
181-
whats_changed = whats_changed_md(repo_name, sorted_merged_pulls)
182-
183-
new_contrib_md = new_contributors_md(repo_name, sorted_merged_pulls, new_contributors)
184-
185-
notes = "## What's changed\n"
186-
for changes in whats_changed:
187-
notes += changes + '\n'
188-
189-
notes += '\n'
190-
191-
if new_contributors:
192-
notes += '## New Contributors\n'
193-
for new_contributor in new_contrib_md:
194-
notes += new_contributor + '\n'
195-
196-
notes += '\n'
197-
198-
if last_release:
199-
notes += full_changelog_md(repository.full_name, last_release.title, next_tag_name)
200-
201-
return notes

tools/release/lib/tests/test_release.py

Lines changed: 0 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -368,141 +368,6 @@ def get_pulls(state: str = 'closed') -> list[PullRequest]:
368368
)
369369

370370

371-
def test_get_sorted_merged_pulls() -> None:
372-
pulls = RepoStub.get_pulls(state='closed')
373-
last_release = None
374-
375-
sorted_merged_pulls = release.get_sorted_merged_pulls(pulls, last_release)
376-
377-
# Should return all the merged pull requests since there is no previous release
378-
assert sorted_merged_pulls == sorted(
379-
[
380-
pull
381-
for pull in pulls
382-
if pull.merged
383-
and pull.base.ref == 'main'
384-
and not pull.title.startswith('chore: release')
385-
and not pull.user.login.startswith('github-actions')
386-
],
387-
key=lambda pull: pull.merged_at,
388-
)
389-
390-
391-
def test_get_last_release() -> None:
392-
releases = RepoStub.get_releases()
393-
394-
# Should return the latest release
395-
last_release = release.get_last_release(releases)
396-
assert last_release.created_at == datetime(2023, 4, 1)
397-
398-
# Should return None (in case there are no releases yet)
399-
last_release = release.get_last_release([])
400-
assert last_release is None
401-
402-
403-
def test_get_old_contributors() -> None:
404-
last_release = release.get_last_release(RepoStub.get_releases())
405-
406-
old_contributors = release.get_old_contributors(RepoStub.get_pulls(), last_release)
407-
408-
# Should return contributors until last release, including co-authors
409-
assert old_contributors == {
410-
'contributor_2',
411-
'contributor_3',
412-
'contributor_4',
413-
'old_contrib_coauthor',
414-
'old_contrib_coauthor2',
415-
}
416-
417-
418-
def test_get_new_contributors() -> None:
419-
last_release = release.get_last_release(RepoStub.get_releases())
420-
all_pulls = RepoStub.get_pulls()
421-
422-
# merged pulls after last release
423-
merged_pulls = release.get_sorted_merged_pulls(all_pulls, last_release)
424-
old_contributors = release.get_old_contributors(all_pulls, last_release)
425-
426-
# Should return a List sorted in alphabetic order with only the new contributors since
427-
# last release
428-
new_contributors = release.get_new_contributors(old_contributors, merged_pulls)
429-
430-
assert new_contributors == [
431-
'new_contributor_1',
432-
'new_contributor_2',
433-
'new_contributor_coauthor1',
434-
'new_contributor_coauthor2',
435-
'new_contributor_coauthor3',
436-
'new_contributor_coauthor4',
437-
]
438-
439-
440-
def test_whats_changed_md() -> None:
441-
repo_stub = RepoStub()
442-
last_release = release.get_last_release(RepoStub.get_releases())
443-
all_pulls = RepoStub.get_pulls()
444-
# merged pulls after last release
445-
merged_pulls = release.get_sorted_merged_pulls(all_pulls, last_release)
446-
447-
whats_changed = release.whats_changed_md(repo_stub.full_name, merged_pulls)
448-
449-
assert whats_changed == [
450-
'* Feature 8 by @new_contributor_1, @new_contributor_coauthor3 and @new_contributor_coauthor4 in https://github.com/ada-url/ada/pull/15',
451-
'* Feature 9 by @new_contributor_2 and @new_contributor_coauthor1 in https://github.com/ada-url/ada/pull/13',
452-
'* Feature 7 by @contributor_3 and @new_contributor_coauthor2 in https://github.com/ada-url/ada/pull/14',
453-
]
454-
455-
456-
def test_new_contributors_md() -> None:
457-
repo_stub = RepoStub()
458-
last_release = release.get_last_release(RepoStub.get_releases())
459-
all_pulls = RepoStub.get_pulls()
460-
461-
merged_pulls = release.get_sorted_merged_pulls(all_pulls, last_release)
462-
old_contributors = release.get_old_contributors(all_pulls, last_release)
463-
new_contributors = release.get_new_contributors(old_contributors, merged_pulls)
464-
465-
# Should return a markdown containing the new contributors and their first contribution
466-
new_contributors_md = release.new_contributors_md(repo_stub.full_name, merged_pulls, new_contributors)
467-
468-
assert new_contributors_md == [
469-
'* @new_contributor_2 and @new_contributor_coauthor1 made their first contribution in https://github.com/ada-url/ada/pull/13',
470-
'* @new_contributor_coauthor2 made their first contribution in https://github.com/ada-url/ada/pull/14',
471-
'* @new_contributor_1, @new_contributor_coauthor3 and @new_contributor_coauthor4 made their first contribution in https://github.com/ada-url/ada/pull/15', # noqa: E501
472-
]
473-
474-
475-
def test_full_changelog_md() -> None:
476-
repo_stub = RepoStub()
477-
last_tag = release.get_last_release(repo_stub.get_releases())
478-
479-
full_changelog = release.full_changelog_md(repo_stub.full_name, last_tag.title, 'v3.0.0')
480-
assert full_changelog == '**Full Changelog**: https://github.com/ada-url/ada/compare/v1.0.3...v3.0.0'
481-
482-
full_changelog = release.full_changelog_md(repo_stub.full_name, None, 'v3.0.0')
483-
assert full_changelog is None
484-
485-
486-
def test_contruct_release_notes() -> None:
487-
repo_stub = RepoStub()
488-
489-
notes = release.contruct_release_notes(repo_stub, 'v3.0.0')
490-
assert (
491-
notes
492-
== "## What's changed\n"
493-
+ '* Feature 8 by @new_contributor_1, @new_contributor_coauthor3 and @new_contributor_coauthor4 in https://github.com/ada-url/ada/pull/15\n'
494-
+ '* Feature 9 by @new_contributor_2 and @new_contributor_coauthor1 in https://github.com/ada-url/ada/pull/13\n'
495-
+ '* Feature 7 by @contributor_3 and @new_contributor_coauthor2 in https://github.com/ada-url/ada/pull/14\n'
496-
+ '\n'
497-
+ '## New Contributors\n'
498-
+ '* @new_contributor_2 and @new_contributor_coauthor1 made their first contribution in https://github.com/ada-url/ada/pull/13\n'
499-
+ '* @new_contributor_coauthor2 made their first contribution in https://github.com/ada-url/ada/pull/14\n'
500-
+ '* @new_contributor_1, @new_contributor_coauthor3 and @new_contributor_coauthor4 made their first contribution in https://github.com/ada-url/ada/pull/15\n' # noqa: E501
501-
+ '\n'
502-
+ '**Full Changelog**: https://github.com/ada-url/ada/compare/v1.0.3...v3.0.0'
503-
)
504-
505-
506371
def test_is_valid_tag() -> None:
507372
assert release.is_valid_tag('v1.0.0') is True
508373
assert release.is_valid_tag('v1.1.1') is True
@@ -511,14 +376,3 @@ def test_is_valid_tag() -> None:
511376
assert release.is_valid_tag('v1.0.0.0') is False
512377
assert release.is_valid_tag('1.0.0') is False
513378
assert release.is_valid_tag('1.0.1') is False
514-
515-
516-
def test_multiple_contributors_mention_md() -> None:
517-
contributors = ['contrib1', 'contrib2', 'contrib3', 'contrib4']
518-
519-
md_contributors_mention = release.multiple_contributors_mention_md(contributors)
520-
assert md_contributors_mention == '@contrib1, @contrib2, @contrib3 and @contrib4'
521-
522-
contributors = ['contrib1']
523-
md_contributors_mention = release.multiple_contributors_mention_md(contributors)
524-
assert md_contributors_mention == '@contrib1'

0 commit comments

Comments
 (0)