forked from Flexget/Flexget
-
Notifications
You must be signed in to change notification settings - Fork 0
/
update-changelog.py
157 lines (141 loc) · 6.03 KB
/
update-changelog.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
from __future__ import unicode_literals, division, absolute_import
import collections
import datetime
import io
import re
import sys
from git import Repo
class MDChangeSet(object):
"""Represets a markdown changeset for a single version."""
CATEGORIES = [
('### Added\n', ['add', 'added', 'feature']),
('### Changed\n', ['change', 'changed', 'update']),
('### Fixed\n', ['fix', 'fixed']),
('### Deprecated\n', ['deprecate', 'deprecated']),
('### Removed\n', ['remove', 'removed']),
]
def __init__(self):
self.pre_header = ['\n']
self.version_header = ''
self.post_header = []
self.sections = collections.OrderedDict()
self.footer = []
@classmethod
def from_md_lines(cls, lines):
"""Parse an existing markdown changeset section and return the VersionLog instance."""
instance = cls()
instance.pre_header, version_header, tail = isplit('## ', lines)
if version_header:
instance.version_header = version_header
instance.post_header, section, tail = isplit('### ', tail)
while section:
instance.sections[section], section, tail = isplit('### ', tail)
return instance
def parse_message(self, message):
"""
Parses a git commit message and formats and adds any tagged messages to this changeset.
Returns True if one or more changelog messages was found.
"""
found = False
for cat, item in self.change_items(message):
found = True
item = re.sub('#(\d{3,4})', r'[#\1](https://github.com/Flexget/Flexget/issues/\1)', item)
item = '- {0}\n'.format(item)
self.sections.setdefault(cat, ['\n']).insert(0, item)
return found
def change_items(self, message):
"""An iterator of changelog updates from a commit message in the form (category, message)"""
for line in message.split('\n'):
for cat_match in re.finditer('\[(\w+)\]', line):
found_cat = self.cat_lookup(cat_match.group(1))
if found_cat:
line = line.replace(cat_match.group(0), '').strip()
yield found_cat, line
def cat_lookup(self, cat):
"""Return an official category for `cat` tag text."""
for cat_item in self.CATEGORIES:
if cat.lower() in cat_item[1]:
return cat_item[0]
def to_md_lines(self):
"""An iterator over the markdown lines representing this changeset."""
for l in self.pre_header:
yield l
yield self.version_header
for l in self.post_header:
yield l
for section, items in self.sections.items():
yield section
for item in items:
yield item
for l in self.footer:
yield l
def isplit(start_text, iterator):
"""Returns head, match, tail tuple, where match is the first line that starts with `start_text`"""
head = []
iterator = iter(iterator)
for item in iterator:
if item.startswith(start_text):
return head, item, iterator
head.append(item)
return head, None, iterator
if __name__ == '__main__':
try:
filename = sys.argv[1]
except IndexError:
print('No filename specified, using changelog.md')
filename = 'changelog.md'
with io.open(filename, encoding='utf-8') as logfile:
pre_lines, start_comment, tail = isplit('<!---', logfile)
active_lines, end_comment, tail = isplit('<!---', tail)
post_lines = list(tail)
repo = Repo('.')
cur_ver = MDChangeSet.from_md_lines(active_lines)
latestref = re.match('<!---\s*([\d\w]+)', start_comment).group(1)
oldestref = re.match('<!---\s*([\d\w]+)', end_comment).group(1)
released_vers = []
commits = list(repo.iter_commits('{0}..HEAD'.format(latestref), reverse=True))
modified = False
if commits:
tags = {}
for tag in repo.tags:
tags[tag.commit.hexsha] = tag.tag
for commit in commits:
if cur_ver.parse_message(commit.message):
modified = True
if commit.hexsha in tags:
modified = True
# Tag changeset with release date and version and create new current changeset
version = tags[commit.hexsha].tag
release_date = datetime.datetime.fromtimestamp(tags[commit.hexsha].tagged_date).strftime('%Y-%m-%d')
cur_ver.version_header = '## {0} ({1})\n'.format(version, release_date)
diffstartref = oldestref
if oldestref in tags:
diffstartref = tags[oldestref].tag
cur_ver.post_header.insert(0, '[all commits](https://github.com/Flexget/Flexget/compare/{0}...{1})\n'.
format(diffstartref, version))
released_vers.insert(0, cur_ver)
cur_ver = MDChangeSet()
oldestref = commit.hexsha
if cur_ver.sections:
verfile = repo.tree('HEAD')['flexget/_version.py'].data_stream.read()
__version__ = None
try:
exec(verfile) # pylint: disable=W0122
except Exception:
pass
new_version_header = '## {0} (unreleased)\n'.format(__version__)
if new_version_header != cur_ver.version_header:
cur_ver.version_header = new_version_header
modified = True
if modified:
print('Writing modified changelog.')
with io.open(filename, 'w', encoding='utf-8') as logfile:
logfile.writelines(pre_lines)
logfile.write('<!---{0}--->\n'.format(commit.hexsha))
logfile.writelines(cur_ver.to_md_lines())
logfile.write('<!---{0}--->\n'.format(oldestref))
for ver in released_vers:
logfile.writelines(ver.to_md_lines())
logfile.writelines(post_lines)
else:
print('No updates to write.')