import argparse
import collections
import os
import re
import shutil
import subprocess
import sys
import time
import getpass
from shlex import quote
VERBOSE = False
QUIET = False
dev_null_fd = None
z40 = '0000000000000000000000000000000000000000'
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def log(*args, **kwargs):
if QUIET:
return
print(*args, **kwargs)
def log_verbose(*args, **kwargs):
if not VERBOSE:
return
print(*args, **kwargs)
def die(msg):
eprint(msg)
sys.exit(1)
def ask_confirm(prompt):
while True:
query = input('%s (y/N): ' % (prompt))
if query.lower() not in ['y', 'n', '']:
print('Expect y or n!')
continue
return query.lower() == 'y'
def get_dev_null():
global dev_null_fd
if dev_null_fd is None:
dev_null_fd = open(os.devnull, 'w')
return dev_null_fd
def shell(cmd, strip=True, cwd=None, stdin=None, die_on_failure=True,
ignore_errors=False, text=True, print_raw_stderr=False):
quoted_cmd = [quote(arg) for arg in cmd]
cwd_msg = ''
if cwd:
cwd_msg = ' in %s' % cwd
log_verbose('Running%s: %s' % (cwd_msg, ' '.join(quoted_cmd)))
err_pipe = subprocess.PIPE
if ignore_errors:
err_pipe = get_dev_null()
start = time.time()
p = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=err_pipe,
stdin=subprocess.PIPE,
universal_newlines=text)
stdout, stderr = p.communicate(input=stdin)
elapsed = time.time() - start
log_verbose('Command took %0.1fs' % elapsed)
if p.returncode == 0 or ignore_errors:
if stderr and not ignore_errors:
if not print_raw_stderr:
eprint('`%s` printed to stderr:' % ' '.join(quoted_cmd))
eprint(stderr.rstrip())
if strip:
if text:
stdout = stdout.rstrip('\r\n')
else:
stdout = stdout.rstrip(b'\r\n')
if VERBOSE:
for l in stdout.splitlines():
log_verbose('STDOUT: %s' % l)
return stdout
err_msg = '`%s` returned %s' % (' '.join(quoted_cmd), p.returncode)
eprint(err_msg)
if stderr:
eprint(stderr.rstrip())
if die_on_failure:
sys.exit(2)
raise RuntimeError(err_msg)
def git(*cmd, **kwargs):
return shell(['git'] + list(cmd), **kwargs)
def get_revs_to_push(range):
commits = git('rev-list', range).splitlines()
commits.reverse()
return commits
def handle_push(args, local_ref, local_sha, remote_ref, remote_sha):
log_verbose('Handle push, reproduce with '
'`echo %s %s %s %s | pre-push.py %s %s'
% (local_ref, local_sha, remote_ref, remote_sha, args.remote,
args.url))
if local_sha == z40:
if not ask_confirm('Are you sure you want to delete "%s" on remote "%s"?' % (remote_ref, args.url)):
die("Aborting")
return
if remote_sha == z40:
if not ask_confirm('Are you sure you want to push a new branch/tag "%s" on remote "%s"?' % (remote_ref, args.url)):
die("Aborting")
range=local_sha
return
else:
range='%s..%s' % (remote_sha, local_sha)
if "commit" not in git('cat-file','-t', remote_sha, ignore_errors=True):
return
revs = get_revs_to_push(range)
if not revs:
return
print('Pushing to "%s" on remote "%s"' % (remote_ref, args.url))
for sha in revs:
print(' - ' + git('show', '--oneline', '--quiet', sha))
if len(revs) > 1:
if not ask_confirm('Are you sure you want to push %d commits?' % len(revs)):
die('Aborting')
for sha in revs:
msg = git('log', '--format=%B', '-n1', sha)
if 'Differential Revision' not in msg:
continue
for line in msg.splitlines():
for tag in ['Summary', 'Reviewers', 'Subscribers', 'Tags']:
if line.startswith(tag + ':'):
eprint('Please remove arcanist tags from the commit message (found "%s" tag in %s)' % (tag, sha[:12]))
if len(revs) == 1:
eprint('Try running: llvm/utils/git/arcfilter.sh')
die('Aborting (force push by adding "--no-verify")')
return
if __name__ == '__main__':
if not shutil.which('git'):
die('error: cannot find git command')
argv = sys.argv[1:]
p = argparse.ArgumentParser(
prog='pre-push', formatter_class=argparse.RawDescriptionHelpFormatter,
description=__doc__)
verbosity_group = p.add_mutually_exclusive_group()
verbosity_group.add_argument('-q', '--quiet', action='store_true',
help='print less information')
verbosity_group.add_argument('-v', '--verbose', action='store_true',
help='print more information')
p.add_argument('remote', type=str, help='Name of the remote')
p.add_argument('url', type=str, help='URL for the remote')
args = p.parse_args(argv)
VERBOSE = args.verbose
QUIET = args.quiet
lines = sys.stdin.readlines()
sys.stdin = open('/dev/tty', 'r')
for line in lines:
local_ref, local_sha, remote_ref, remote_sha = line.split()
handle_push(args, local_ref, local_sha, remote_ref, remote_sha)