222 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
#!/usr/bin/env python3
 | 
						|
#
 | 
						|
# ======- pre-push - LLVM Git Help Integration ---------*- python -*--========#
 | 
						|
#
 | 
						|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
# See https://llvm.org/LICENSE.txt for license information.
 | 
						|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
#
 | 
						|
# ==------------------------------------------------------------------------==#
 | 
						|
 | 
						|
"""
 | 
						|
pre-push git hook integration
 | 
						|
=============================
 | 
						|
 | 
						|
This script is intended to be setup as a pre-push hook, from the root of the
 | 
						|
repo run:
 | 
						|
 | 
						|
   ln -sf ../../llvm/utils/git/pre-push.py .git/hooks/pre-push
 | 
						|
 | 
						|
From the git doc:
 | 
						|
 | 
						|
  The pre-push hook runs during git push, after the remote refs have been
 | 
						|
  updated but before any objects have been transferred. It receives the name
 | 
						|
  and location of the remote as parameters, and a list of to-be-updated refs
 | 
						|
  through stdin. You can use it to validate a set of ref updates before a push
 | 
						|
  occurs (a non-zero exit code will abort the push).
 | 
						|
"""
 | 
						|
 | 
						|
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():
 | 
						|
    """Lazily create a /dev/null fd for use in shell()"""
 | 
						|
    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):
 | 
						|
    # Escape args when logging for easy repro.
 | 
						|
    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:
 | 
						|
        # Silence errors if requested.
 | 
						|
        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()
 | 
						|
    # Reverse the order so we print the oldest commit first
 | 
						|
    commits.reverse()
 | 
						|
    return commits
 | 
						|
 | 
						|
 | 
						|
def handle_push(args, local_ref, local_sha, remote_ref, remote_sha):
 | 
						|
    '''Check a single push request (which can include multiple revisions)'''
 | 
						|
    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))
 | 
						|
    # Handle request to delete
 | 
						|
    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
 | 
						|
 | 
						|
    # Push a new branch
 | 
						|
    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:
 | 
						|
      # Update to existing branch, examine new commits
 | 
						|
      range='%s..%s' % (remote_sha, local_sha)
 | 
						|
      # Check that the remote commit exists, otherwise let git proceed
 | 
						|
      if "commit" not in git('cat-file','-t', remote_sha, ignore_errors=True):
 | 
						|
          return
 | 
						|
 | 
						|
    revs = get_revs_to_push(range)
 | 
						|
    if not revs:
 | 
						|
        # This can happen if someone is force pushing an older revision to a branch
 | 
						|
        return
 | 
						|
 | 
						|
    # Print the revision about to be pushed commits
 | 
						|
    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)
 |