Skip to content

Instantly share code, notes, and snippets.

@prjemian
Created July 11, 2017 16:40
Show Gist options
  • Select an option

  • Save prjemian/c8c28540332633872abe7d0846f80418 to your computer and use it in GitHub Desktop.

Select an option

Save prjemian/c8c28540332633872abe7d0846f80418 to your computer and use it in GitHub Desktop.
Create release notes for a new relase of a GitHub repository
```
#!/usr/bin/env python
# Coded for both python2 and python3.
'''
Create release notes for a new relase of a GitHub repository.
Assumes that the repository has committed work (tags, pulls,
issues, commits) on a milestone.
example::
create_release_notes.py \
prjemian \
spec2nexus \
2017.522.1 \
2017-07-bugfixes \
--credsfile path/to/__github_creds__.txt
see: https://github.com/prjemian/spec2nexus/wiki/releasenotes__2017-07-bugfixes
'''
from __future__ import print_function
import os, sys
import argparse
import collections
from datetime import datetime
import github
import logging
logger = logging.getLogger(__name__)
CREDS_FILE_NAME = "__github_creds__.txt"
CREDS_FILE_NAME = os.path.join(os.path.dirname(__file__), CREDS_FILE_NAME)
GITHUB_PER_PAGE = 30
def str2time(time_string):
# Tue, 20 Dec 2016 17:35:40 GMT
fmt = "%a, %d %b %Y %H:%M:%S %Z"
return datetime.strptime(time_string, fmt)
class ReleaseNotes(object):
def __init__(self, base, head=None, milestone=None, creds_file_name=None):
self.base = base
self.head = head or "master"
self.milestone_title = milestone
self.milestone = None
self.commit_db = {}
self.db = dict(tags={}, pulls={}, issues={}, commits={})
self.creds_file_name = creds_file_name or CREDS_FILE_NAME
if not os.path.exists(self.creds_file_name):
raise ValueError('Missing file: ' + self.creds_file_name)
def connect(self, organization, repository):
uname, pwd = open(self.creds_file_name, 'r').read().split()
self.gh = github.Github(uname, password=pwd, per_page=GITHUB_PER_PAGE)
logger.debug("uname", uname)
self.user = self.gh.get_user(organization)
self.repo = self.user.get_repo(repository)
def learn(self):
base_commit = None
earliest = None
compare = self.repo.compare(self.base, self.head)
commits = self.db["commits"] = collections.OrderedDict()
for commit in compare.commits:
commits[commit.sha] = commit
# commits = self.db["commits"] = {commit.sha: commit for commit in compare.commits}
for milestone in self.repo.get_milestones():
if milestone.title == self.milestone_title:
self.milestone = milestone
if self.milestone is None:
for milestone in self.repo.get_milestones(state="closed"):
if milestone.title == self.milestone_title:
self.milestone = milestone
tags = self.db["tags"]
for tag in self.repo.get_tags():
if tag.commit.sha in commits:
tags[tag.name] = tag
elif tag.name == self.base:
base_commit = self.repo.get_commit(tag.commit.sha)
earliest = str2time(base_commit.last_modified)
pulls = self.db["pulls"]
for pull in self.repo.get_pulls(state="closed"):
if pull.closed_at > earliest:
pulls[pull.number] = pull
issues = self.db["issues"]
for issue in self.repo.get_issues(milestone=self.milestone, state="closed"):
if self.milestone is not None or issue.closed_at > earliest:
if issue.number not in pulls:
issues[issue.number] = issue
def print_report(self):
print("## " + self.milestone_title)
print("")
if self.milestone is not None:
print("**milestone**: [%s](%s)" % (self.milestone.title, self.milestone.url))
print("")
print("section | number")
print("-"*5, " | ", "-"*5)
print("New Tags | ", len(self.db["tags"]))
print("Pull Requests | ", len(self.db["pulls"]))
print("Issues | ", len(self.db["issues"]))
print("Commits | ", len(self.db["commits"]))
print("")
print("### Tags")
print("")
print("sorted by most recent first")
print("")
for k, tag in sorted(self.db["tags"].items()):
print("* [%s](%s) %s" % (tag.commit.sha[:7], tag.commit.html_url, k))
print("")
print("### Pull Requests")
print("")
print("sorted by increasing pull request number")
print("")
for k, pull in sorted(self.db["pulls"].items()):
state = {True: "merged", False: "closed"}[pull.merged]
print("* [#%d](%s) (%s) %s" % (pull.number, pull.html_url, state, pull.title))
print("")
print("### Issues")
print("")
print("sorted by increasing issue number")
print("")
for k, issue in sorted(self.db["issues"].items()):
if k not in self.db["pulls"]:
print("* [#%d](%s) %s" % (issue.number, issue.html_url, issue.title))
print("")
print("### Commits")
print("")
print("sorted by earliest commit first")
print("")
for k, commit in self.db["commits"].items():
message = commit.commit.message.splitlines()[0]
print("* [%s](%s) %s" % (k[:7], commit.html_url, message))
print("")
def main(
organization,
repository,
base,
head="master",
milestone="NXDL 3.3",
credsfile=CREDS_FILE_NAME):
# github.enable_console_debug_logging()
logger.debug("organization: " + organization)
logger.debug("repository: " + repository)
logger.debug("base: " + base)
logger.debug("head: " + head)
logger.debug("milestone: " + milestone)
logger.debug("credsfile: " + credsfile)
notes = ReleaseNotes(base, head=head, milestone=milestone, creds_file_name=credsfile)
notes.connect(organization, repository)
notes.learn()
notes.print_report()
def parse_command_line():
doc = __doc__.strip()
parser = argparse.ArgumentParser(description=doc)
help_text = "GitHub organization"
parser.add_argument('organization', action='store', help=help_text)
help_text = "GitHub repository"
parser.add_argument('repository', action='store', help=help_text)
help_text = "name of tag to start the range"
parser.add_argument('base', action='store', help=help_text)
help_text = "name of milestone"
parser.add_argument('milestone', action='store', help=help_text)
help_text = "name of tag, branch, SHA to end the range"
help_text += ' (default="master")'
parser.add_argument(
"--head",
action='store',
dest='head',
nargs='?',
help = help_text,
default="master")
help_text = "name of file with GitHub credentials (username and password)"
help_text += ' (default="%s")' % CREDS_FILE_NAME
parser.add_argument(
"--credsfile",
action='store',
dest='credsfile',
nargs='?',
help = help_text,
default=CREDS_FILE_NAME)
return parser.parse_args()
if __name__ == '__main__':
cmd = parse_command_line()
logger.debug("command line arguments: " + str(cmd))
main(
cmd.organization,
cmd.repository,
cmd.base,
head=cmd.head,
milestone=cmd.milestone,
credsfile=cmd.credsfile)
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment