changelog.py (4362B)
1 #!/usr/bin/env python3 2 # Copied from ElementsProject/lightning, thanks! :) 3 4 from collections import namedtuple 5 from datetime import datetime 6 from mako.template import Template 7 import argparse 8 import os 9 import re 10 import requests 11 import shlex 12 import subprocess 13 import sys 14 15 # What sections do we support in the changelog: 16 sections = [ 17 'added', 18 'changed', 19 'deprecated', 20 'fixed', 21 'removed', 22 'experimental', 23 ] 24 25 repo = 'damus-io/damus' 26 27 Entry = namedtuple("Entry", ["commit", "pullreq", "content", "section"]) 28 Link = namedtuple("Link", ["ref", "content", "url"]) 29 30 31 def git(cmd): 32 cmd = shlex.split(cmd) 33 out = subprocess.check_output(['git'] + cmd) 34 return out.decode('UTF-8') 35 36 37 def get_commit_range(): 38 """Find a commit range that we should collect the CHANGELOG for. 39 """ 40 description = git("describe") 41 version = description.split('-')[0] 42 return "{version}..master".format(version=version) 43 44 45 def get_log_entries(commitrange): 46 commit = None 47 logs = git("log {commitrange}".format(commitrange=commitrange)) 48 entries = [] 49 50 for l in logs.split('\n'): 51 m = re.match(r'^commit ([A-Fa-f0-9]{40})$', l) 52 if m: 53 commit = m.group(1) 54 55 m = re.match( 56 r'^\s+Changelog-({}): (.*)$'.format("|".join(sections)), l, re.IGNORECASE) 57 if not m: 58 continue 59 60 # Now try to resolve the pull request that originated this commit: 61 headers = { 62 'Accept': 'application/vnd.github.groot-preview+json', 63 } 64 65 if os.environ.get('GH_TOKEN'): 66 headers['Authorization'] = 'token ' + os.environ.get('GH_TOKEN') 67 68 url = 'https://api.github.com/repos/{repo}/commits/{commit}/pulls'.format(repo=repo, commit=commit) 69 content = requests.get(url, headers=headers).json() 70 if content and content.get(0) is not None: 71 pullreq = content[0]['number'] 72 else: 73 pullreq = None 74 75 e = Entry(commit, pullreq, m.group(2), m.group(1).lower()) 76 entries.append(e) 77 78 return entries 79 80 81 def linkify(entries): 82 links = [] 83 for e in entries: 84 if e.pullreq is not None: 85 links.append(Link( 86 ref='#{}'.format(e.pullreq), 87 content=e.content, 88 url="https://github.com/{repo}/pull/{pullreq}".format(repo=repo, pullreq=e.pullreq) 89 )) 90 return list(set(links)) 91 92 93 def group(entries): 94 groups = {s: [] for s in sections} 95 for e in entries: 96 groups[e.section].append(e) 97 for s in sections: 98 if len(groups[s]) == 0: 99 del groups[s] 100 return groups 101 102 103 def commit_date(commitsha): 104 """Get the date of the specified commit. 105 """ 106 line = git("show -s --format=%ci") 107 dt = datetime.strptime(line.strip(), '%Y-%m-%d %H:%M:%S %z') 108 return dt 109 110 111 template = Template("""<%def name="group(entries)"> 112 % for e in entries: 113 % if e.pullreq is not None: 114 - ${e.content} ([#${e.pullreq}]) 115 % else: 116 - ${e.content} 117 % endif 118 % endfor 119 120 </%def><%def name="group_links(entries)"> 121 % for e in entries: 122 [${e.pullreq}]: https://github.com/${repo}/pull/${e.pullreq} 123 % endfor 124 </%def> 125 126 ${h2} [${version}] - ${date.strftime("%Y-%m-%d")} 127 128 % for section in sections: 129 ${h3} ${section.capitalize()} 130 ${group(groups[section]) | trim} 131 % endfor 132 133 % for l in links: 134 [${l.ref}]: ${l.url} 135 % endfor 136 [${version}]: https://github.com/${repo}/releases/tag/v${version}""") 137 138 139 if __name__ == "__main__": 140 parser = argparse.ArgumentParser( 141 description='Generate a changelog summary for a given commit range' 142 ) 143 parser.add_argument('commitrange', type=str, nargs='?', 144 help='Range of commits to consider (format: <from_commit>..<to_commit>', 145 default=get_commit_range()) 146 147 args = parser.parse_args() 148 149 if '..' not in args.commitrange: 150 print("Commit range must include '..' to separate 'from_commit' and 'to_commit'") 151 sys.exit(1) 152 153 fromcommit, tocommit = args.commitrange.split('..') 154 entries = get_log_entries(args.commitrange) 155 groups = group(entries) 156 date = commit_date(tocommit) 157 158 print(template.render( 159 groups=groups, 160 repo=repo, 161 sections=groups.keys(), 162 h2='##', 163 h3='###', 164 version=tocommit[1:], 165 date=date, 166 links=linkify(entries), 167 )) 168 169