diff --git a/release.py b/release.py index 5a3fa29c..080932b5 100755 --- a/release.py +++ b/release.py @@ -1,22 +1,48 @@ #!/usr/bin/env python from argparse import ArgumentParser, Namespace -from datetime import datetime -from os import path +from collections import OrderedDict from configparser import RawConfigParser +from datetime import datetime +from json import dumps +from os import path from subprocess import Popen, PIPE +from jinja2 import Environment, BaseLoader +from requests import patch GIT_COMMANDS = { "get_tag": ["git describe --tags --abbrev=0"], - "create_new_branch": ["git checkout -b {new_version} master"], - "commit_version_change": ["git commit -m 'Bumping up version from {current_version} to {new_version}'"], - "push_new_branch": ["git push origin {new_version}"], + "commit_version_change": [ + "git add . && git commit -m 'Bumping up version from {current_version} to {new_version}'"], "create_new_tag": ["git tag -a {new_version} -m 'Bumping up version from {current_version} to {new_version}'"], "push_tag": ["git push origin {new_version}"], - "get_change_log": ['git log --no-merges --pretty=format:"%h: %cn: %s" {current_version}..'] + "get_change_log": ['git log --no-merges --pretty=format:"%h::: %cn::: %s" {current_version}..'] } +RELASE_NOTE_TEMPLATE = """ +# {{ release_name }} - {% now 'utc', '%Y-%m-%d' %} + +To see the exhaustive list of pull requests included in this release see: +https://github.com/huge-success/sanic/milestone/{{milestone}}?closed=1 + +# Changelog +{% for row in changelogs %} +* {{ row -}} +{% endfor %} + +# Credits +{% for author in authors %} +* {{ author -}} +{% endfor %} +""" + +JINJA_RELASE_NOTE_TEMPLATE = Environment( + loader=BaseLoader, extensions=['jinja2_time.TimeExtension']).from_string(RELASE_NOTE_TEMPLATE) + +RELEASE_NOTE_UPDATE_URL = \ + "https://api.github.com/repos/huge-success/sanic/releases/tags/{new_version}?access_token={token}" + def _run_shell_command(command: list): try: @@ -24,8 +50,10 @@ def _run_shell_command(command: list): shell=True) output, error = process.communicate() return_code = process.returncode - return output, error, return_code + return output.decode("utf-8"), error, return_code except: + import traceback + traceback.print_exc() return None, None, -1 @@ -73,35 +101,69 @@ def _get_current_tag(git_command_name="get_tag"): def _update_release_version_for_sanic(current_version, new_version, config_file): - old_version_line = '__version__ = "{current_version}"'.format(current_version=current_version) - new_version_line = '__version__ = "{new_version}"'.format(new_version=new_version) - - with open("sanic/__init__.py") as init_file: - data = init_file.read() - - new_data = data.replace(old_version_line, new_version_line) - with open("sanic/__init__.py", "w") as init_file: - init_file.write(new_data) - config_parser = RawConfigParser() with open(config_file) as cfg: config_parser.read_file(cfg) config_parser.set("version", "current_version", new_version) + version_file = config_parser.get("version", "file") + current_version_line = config_parser.get("version", "current_version_pattern").format( + current_version=current_version) + new_version_line = config_parser.get("version", "new_version_pattern").format(new_version=new_version) + + with open(version_file) as init_file: + data = init_file.read() + + new_data = data.replace(current_version_line, new_version_line) + with open(version_file, "w") as init_file: + init_file.write(new_data) + with open(config_file, "w") as config: config_parser.write(config) command = GIT_COMMANDS.get("commit_version_change") command[0] = command[0].format(new_version=new_version, current_version=current_version) - _, _, ret = _run_shell_command(command) + _, err, ret = _run_shell_command(command) if int(ret) != 0: - print("Failed to Commit Version upgrade changes to Sanic") + print("Failed to Commit Version upgrade changes to Sanic: {}".format(err.decode("utf-8"))) exit(1) -def _tag_release(new_version, current_version): +def _generate_change_log(current_version: str = None): global GIT_COMMANDS - for command_name in ["push_new_branch", "create_new_tag", "push_tag"]: + command = GIT_COMMANDS.get("get_change_log") + command[0] = command[0].format(current_version=current_version) + output, error, ret = _run_shell_command(command=command) + if not len(str(output)): + print("Unable to Fetch Change log details to update the Release Note") + exit(1) + + commit_details = OrderedDict() + commit_details["authors"] = dict() + commit_details["commits"] = list() + + for line in str(output).split("\n"): + commit, author, description = line.split(":::") + if 'GitHub' not in author: + commit_details["authors"][author] = 1 + commit_details["commits"].append(" - ".join([commit, description])) + + return commit_details + + +def _generate_markdown_document(milestone, release_name, current_version, release_version): + global JINJA_RELASE_NOTE_TEMPLATE + release_name = release_name or release_version + change_log = _generate_change_log(current_version=current_version) + return JINJA_RELASE_NOTE_TEMPLATE.render( + release_name=release_name, milestone=milestone, changelogs=change_log["commits"], + authors=change_log["authors"].keys()) + + +def _tag_release(new_version, current_version, milestone, release_name, token): + global GIT_COMMANDS + global RELEASE_NOTE_UPDATE_URL + for command_name in ["create_new_tag", "push_tag"]: command = GIT_COMMANDS.get(command_name) command[0] = command[0].format(new_version=new_version, current_version=current_version) out, error, ret = _run_shell_command(command=command) @@ -109,16 +171,33 @@ def _tag_release(new_version, current_version): print("Failed to execute the command: {}".format(command[0])) exit(1) + change_log = _generate_markdown_document(milestone, release_name, current_version, new_version) + + body = { + "name": release_name or new_version, + "body": change_log + } + + headers = { + "content-type": "application/json" + } + + response = patch(RELEASE_NOTE_UPDATE_URL.format(new_version=new_version, token=token), + data=dumps(body), headers=headers) + response.raise_for_status() + def release(args: Namespace): current_tag = _get_current_tag() current_version = _fetch_current_version(args.config) if current_tag and current_version not in current_tag: - print("Tag mismatch between what's in git and what was provided by --current-version") + print("Tag mismatch between what's in git and what was provided by --current-version. " + "Existing: {}, Give: {}".format(current_tag, current_version)) exit(1) - new_version = _get_new_version(args.config, current_version, args.micro_release) + new_version = args.release_version or _get_new_version(args.config, current_version, args.micro_release) _update_release_version_for_sanic(current_version=current_version, new_version=new_version, config_file=args.config) - _tag_release(current_version=current_version, new_version=new_version) + _tag_release(current_version=current_version, new_version=new_version, + milestone=args.milestone, release_name=args.release_name, token=args.token) if __name__ == '__main__': @@ -129,7 +208,13 @@ if __name__ == '__main__': cli.add_argument("--current-version", "-cv", help="Current Version to default in case if you don't want to " "use the version configuration files", default=None, required=False) - cli.add_argument("--config", "-c", help="Configuration file used for release", default="./setup.cfg", required=False) + cli.add_argument("--config", "-c", help="Configuration file used for release", default="./setup.cfg", + required=False) + cli.add_argument("--token", "-t", help="Git access token with necessary access to Huge Sanic Org", + required=True) + cli.add_argument("--milestone", "-ms", help="Git Release milestone information to include in relase note", + required=True) + cli.add_argument("--release-name", "-n", help="Release Name to use if any", required=False) cli.add_argument("--micro-release", "-m", help="Micro Release with patches only", default=False, action='store_true', required=False) args = cli.parse_args() diff --git a/setup.cfg b/setup.cfg index c049420c..043652fd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,4 +14,7 @@ multi_line_output = 3 not_skip = __init__.py [version] -current_version = 0.8.3 \ No newline at end of file +current_version = 18.12.0 +file = sanic/__init__.py +current_version_pattern = __version__ = "{current_version}" +new_version_pattern = __version__ = "{new_version}"