Create a gist now

Instantly share code, notes, and snippets.

Script for automated version tagging & changelog updates.
#!/usr/bin/env python
"""This script generates release notes for each merged pull request from
git merge-commit messages.
`python <start_commit> <end_commit> [--output {file,stdout}]`
For example, if you wanted to find the diff between version 1.0 and 1.2,
and write the output to the release notes file, you would type the
`python 1.0 1.2 -o file`
import os.path as op
import re
import subprocess
from collections import deque
def commit_msgs(start_commit, end_commit):
"""Run the git command that outputs the merge commits (both subject
and body) to stdout, and return the output.
fmt_string = ("'%s%n* [#{pr_num}]"
"(" + PROJECT_URI + "/{pr_num}) - %b'")
return subprocess.check_output([
"--pretty=format:%s" % fmt_string,
"--merges", "%s..%s" % (start_commit, end_commit)])
def release_note_lines(msgs):
"""Parse the lines from git output and format the strings using the
pull request number.
ptn = r"Merge pull request #(\d+).*\n([^\n]*)'$"
pairs = re.findall(ptn, msgs, re.MULTILINE)
return deque(body.format(pr_num=pr_num) for pr_num, body in pairs)
def release_header_line(version, release_date=None):
release_date = release_date or'%Y/%m/%d')
return "## %s - %s" % (version, release_date)
def prepend(filename, lines, release_header=False):
"""Write `lines` (i.e. release notes) to file `filename`."""
if op.exists(filename):
with open(filename, 'r+') as f:
first_line =, 0)
f.write('\n\n'.join([lines, first_line]))
with open(filename, 'w') as f:
if __name__ == "__main__":
import argparse
import datetime
parser = argparse.ArgumentParser()
parser.add_argument('start_commit', metavar='START_COMMIT_OR_TAG')
parser.add_argument('end_commit', metavar='END_COMMIT_OR_TAG')
parser.add_argument('--filepath', '-f',
help="Absolute path to output file.")
parser.add_argument('--tag', '-t', metavar='NEW_TAG')
'--date', '-d', metavar='RELEASE_DATE',
help="Date of release for listed patch notes. Use yyyy/mm/dd format.")
args = parser.parse_args()
start, end = args.start_commit, args.end_commit
lines = release_note_lines(commit_msgs(start, end))
if args.tag:
lines = '\n'.join(lines)
if args.filepath:
filename = op.abspath(args.filepath)
prepend(filename, lines)
print lines
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment