Created
July 11, 2017 16:40
-
-
Save prjemian/c8c28540332633872abe7d0846f80418 to your computer and use it in GitHub Desktop.
Create release notes for a new relase of a GitHub repository
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ``` | |
| #!/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