Skip to content

Commit 3cda3f4

Browse files
Merge pull request #80 from crnbrdrck/master
Adding CHANGELOG auto generation script
2 parents 7dab8f4 + 4b93e22 commit 3cda3f4

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

changes.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/usr/bin/python
2+
"""
3+
Small python script that, when run, will update the CHANGELOG with information
4+
about all merged pull requests since the previous release.
5+
6+
This script must be run after tagging the latest version
7+
It checks the log of commits since the previous tag and parses it
8+
"""
9+
import re
10+
import subprocess
11+
import sys
12+
from datetime import datetime
13+
14+
# Regex patterns
15+
RELEASE_MD_PATTERN = re.compile(r'## \[(\d+\.\d+\.\d+)\]')
16+
MERGED_PR_PATTERN = re.compile(
17+
r'([0-9a-f]{7}) Merge pull request #(\d+) from (.+)/.+'
18+
)
19+
TAG_PATTERN = re.compile(
20+
r'refs/tags/v(\d+\.\d+\.\d+) (\w{3} \w{3} \d{1,2} \d{2}:\d{2}:\d{2} \d{4})'
21+
)
22+
23+
# PR Type terms
24+
FIX_TERMS = ['fix', 'change', 'update']
25+
26+
27+
# Helper functions
28+
def generate_pr_link(pr_num):
29+
"""
30+
Returns a markdown link to a PR in this repo given its number
31+
"""
32+
return (
33+
'[PR #{0}](https://github.com/sendgrid/smtpapi-python/pulls/{0})'
34+
).format(pr_num)
35+
36+
37+
def generate_user_link(user):
38+
"""
39+
Returns a markdown link to a user
40+
"""
41+
return '[@{0}](https://github.com/{0})'.format(user)
42+
43+
44+
# Get latest tag
45+
command = ['git', 'tag', '--format=%(refname) %(creatordate)']
46+
res = subprocess.run(command, capture_output=True, text=True)
47+
if res.returncode != 0:
48+
print('Error occurred when running git tag command:', str(res.stderr))
49+
sys.exit(1)
50+
# Get the last line and get the tag number
51+
latest_release_match = TAG_PATTERN.match(
52+
list(filter(None, res.stdout.split('\n')))[-1],
53+
)
54+
latest_release = latest_release_match[1]
55+
latest_release_date = datetime.strptime(
56+
latest_release_match[2], '%a %b %d %H:%M:%S %Y',
57+
)
58+
print('Generating CHANGELOG for', latest_release)
59+
60+
# Read in the CHANGELOG file first
61+
with open('CHANGELOG.md') as f:
62+
# Read the text in as a list of lines
63+
old_text = f.readlines()
64+
# Get the latest release (top of the CHANGELOG)
65+
for line in old_text:
66+
match = RELEASE_MD_PATTERN.match(line)
67+
if match:
68+
prev_release = match[1]
69+
break
70+
71+
if latest_release == prev_release:
72+
print(
73+
'The latest git tag matches the last release in the CHANGELOG. '
74+
'Please tag the repository before running this script.'
75+
)
76+
sys.exit(1)
77+
78+
# Use git log to list all commits between that tag and HEAD
79+
command = 'git log --oneline v{}..@'.format(prev_release).split(' ')
80+
res = subprocess.run(command, capture_output=True, text=True)
81+
if res.returncode != 0:
82+
print('Error occurred when running git log command:', str(res.stderr))
83+
sys.exit(1)
84+
85+
# Parse the output from the above command to find all commits for merged PRs
86+
merge_commits = []
87+
for line in res.stdout.split('\n'):
88+
match = MERGED_PR_PATTERN.match(line)
89+
if match:
90+
merge_commits.append(match)
91+
92+
# Determine the type of PR from the commit message
93+
added, fixes = [], []
94+
for commit in merge_commits:
95+
# Get the hash of the commit and get the message of it
96+
commit_sha = commit[1]
97+
command = 'git show {} --format=format:%B'.format(commit_sha).split(' ')
98+
res = subprocess.run(command, capture_output=True, text=True)
99+
out = res.stdout.lower()
100+
is_added = True
101+
102+
# When storing we need the PR title, number and user
103+
data = {
104+
# 3rd line of the commit message is the PR title
105+
'title': out.split('\n')[2],
106+
'number': commit[2],
107+
'user': commit[3],
108+
}
109+
110+
for term in FIX_TERMS:
111+
if term in out:
112+
fixes.append(data)
113+
is_added = False
114+
break
115+
if is_added:
116+
added.append(data)
117+
118+
# Now we need to write out the CHANGELOG again
119+
with open('CHANGELOG.md', 'w') as f:
120+
# Write out the header lines first
121+
for i in range(0, 3):
122+
f.write(old_text[i])
123+
124+
# Create and write out the new version information
125+
latest_release_date_string = latest_release_date.strftime('%Y-%m-%d')
126+
f.write('## [{}] - {} ##\n'.format(
127+
latest_release,
128+
latest_release_date_string,
129+
))
130+
# Add the stuff that was added
131+
f.write('### Added\n')
132+
for commit in added:
133+
f.write('- {}: {}{} (via {})\n'.format(
134+
generate_pr_link(commit['number']),
135+
commit['title'],
136+
'.' if commit['title'][-1] != '.' else '',
137+
generate_user_link(commit['user'])
138+
))
139+
f.write('\n')
140+
# Add the fixes
141+
f.write('### Fixes\n')
142+
for commit in fixes:
143+
f.write('- {}: {}{} (via {})\n'.format(
144+
generate_pr_link(commit['number']),
145+
commit['title'],
146+
'.' if commit['title'][-1] != '.' else '',
147+
generate_user_link(commit['user'])
148+
))
149+
f.write('\n')
150+
151+
# Add the old stuff
152+
for i in range(3, len(old_text)):
153+
f.write(old_text[i])

0 commit comments

Comments
 (0)