Skip to content

Instantly share code, notes, and snippets.

@pior
Last active March 31, 2018 00:51
Show Gist options
  • Save pior/710c18cc516bdf646b1f7965634f7272 to your computer and use it in GitHub Desktop.
Save pior/710c18cc516bdf646b1f7965634f7272 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import argparse
import fileinput
import re
import sys
from subprocess import run, PIPE, STDOUT
def title(msg):
print(f'\n🍄 {msg}\n')
def fatal(msg):
print(f'\n💥 Fatal: {msg}\n')
sys.exit(1)
def capture(args):
result = run(args, check=True, stdout=PIPE)
return result.stdout.strip().decode()
def get_git_branch():
return capture(['git', 'rev-parse', '--abbrev-ref', 'HEAD'])
def get_git_tags(remote=False):
if remote:
output = capture(['git', 'ls-remote', '--tags', 'origin'])
else:
output = capture(['git', 'show-ref', '--tags'])
lines = re.findall(r'refs/tags/([^^\n]+)', output)
return list(set(lines))
def is_git_clean():
result = run(['git', 'diff-index', '--quiet', 'HEAD', '--'], check=False, stdout=PIPE)
return result.returncode == 0
def update_version_setup_py(version):
changed = False
for line in fileinput.input('setup.py', inplace=True):
if re.match(r'VERSION\s*=', line):
print(f"VERSION = '{version}' # maintained by release tool")
changed = True
else:
print(line, end='')
return changed
def is_twine_installed():
result = run(['which', 'twine'])
return result.returncode == 0
def release(options):
version_string = options.version
if version_string.startswith('v'):
fatal('A version can\'t begin with a v')
version = f'v{version_string}'
# Checks
if options.upload:
if not is_twine_installed():
fatal('twine is not installed')
if options.only_on_branch:
current_branch = get_git_branch()
if current_branch != options.only_on_branch:
fatal(f'not on the {options.only_on_branch} branch. ({current_branch})')
if not is_git_clean():
fatal('uncommited files')
if version in get_git_tags(remote=False):
fatal(f'tag already exists locally ({version})')
if version in get_git_tags(remote=True):
fatal(f'tag already exists remotely ({version})')
# Prepare
title('Updating setup.py...')
changed = update_version_setup_py(version_string)
if not changed:
fatal('failed to update setup.py')
# Build
title('Building distribution...')
run(['rm', '-rf', 'dist'], check=True)
result = run(['python', 'setup.py', 'sdist', 'bdist_wheel'], stdout=PIPE)
if result.returncode != 0:
fatal('failed to build distribution')
# Create
title('Create the release commit')
run(['git', 'add', 'setup.py'], check=True)
run(['git', 'commit', '-m', f'Release {version}'], check=True)
title('Creating tag')
run(['git', 'tag', '-a', '-m', f'Release tag for {version}', version], check=True)
# Post actions
if options.push:
title('Pushing to git upstream')
run(['git', 'push', '--follow-tags'], check=True)
else:
title('Don\'t forget to push:\n git push --follow-tags\n')
if options.upload:
title('Uploading to PyPI')
run(['twine', 'upload', 'dist/*'], check=True)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('version', help='Version string without the "v"')
parser.add_argument('--only-on-branch', type=str, metavar='BRANCH-NAME')
parser.add_argument('--push', default=False, action='store_true')
parser.add_argument('--upload', default=False, action='store_true')
args = parser.parse_args()
release(args)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment