forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			331 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
| #!/usr/bin/env python
 | |
| #
 | |
| # ======- git-llvm - 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
 | |
| #
 | |
| # ==------------------------------------------------------------------------==#
 | |
| 
 | |
| """
 | |
| git-llvm integration
 | |
| ====================
 | |
| 
 | |
| This file provides integration for git.
 | |
| 
 | |
| The git llvm push sub-command can be used to push changes to GitHub.  It is
 | |
| designed to be a thin wrapper around git, and its main purpose is to
 | |
| detect and prevent merge commits from being pushed to the main repository.
 | |
| 
 | |
| Usage:
 | |
| 
 | |
| git-llvm push <upstream-branch>
 | |
| 
 | |
| This will push changes from the current HEAD to the branch <upstream-branch>.
 | |
| 
 | |
| """
 | |
| 
 | |
| from __future__ import print_function
 | |
| import argparse
 | |
| import collections
 | |
| import os
 | |
| import re
 | |
| import shutil
 | |
| import subprocess
 | |
| import sys
 | |
| import time
 | |
| import getpass
 | |
| assert sys.version_info >= (2, 7)
 | |
| 
 | |
| try:
 | |
|     dict.iteritems
 | |
| except AttributeError:
 | |
|     # Python 3
 | |
|     def iteritems(d):
 | |
|         return iter(d.items())
 | |
| else:
 | |
|     # Python 2
 | |
|     def iteritems(d):
 | |
|         return d.iteritems()
 | |
| 
 | |
| try:
 | |
|     # Python 3
 | |
|     from shlex import quote
 | |
| except ImportError:
 | |
|     # Python 2
 | |
|     from pipes import quote
 | |
| 
 | |
| # It's *almost* a straightforward mapping from the monorepo to svn...
 | |
| LLVM_MONOREPO_SVN_MAPPING = {
 | |
|     d: (d + '/trunk')
 | |
|     for d in [
 | |
|         'clang-tools-extra',
 | |
|         'compiler-rt',
 | |
|         'debuginfo-tests',
 | |
|         'dragonegg',
 | |
|         'klee',
 | |
|         'libc',
 | |
|         'libclc',
 | |
|         'libcxx',
 | |
|         'libcxxabi',
 | |
|         'libunwind',
 | |
|         'lld',
 | |
|         'lldb',
 | |
|         'llvm',
 | |
|         'openmp',
 | |
|         'parallel-libs',
 | |
|         'polly',
 | |
|         'pstl',
 | |
|     ]
 | |
| }
 | |
| LLVM_MONOREPO_SVN_MAPPING.update({'clang': 'cfe/trunk'})
 | |
| LLVM_MONOREPO_SVN_MAPPING.update({'': 'monorepo-root/trunk'})
 | |
| 
 | |
| SPLIT_REPO_NAMES = {'llvm-' + d: d + '/trunk'
 | |
|                     for d in ['www', 'zorg', 'test-suite', 'lnt']}
 | |
| 
 | |
| VERBOSE = False
 | |
| QUIET = False
 | |
| dev_null_fd = None
 | |
| 
 | |
| GIT_ORG = 'llvm'
 | |
| GIT_REPO = 'llvm-project'
 | |
| GIT_URL = 'github.com/{}/{}.git'.format(GIT_ORG, GIT_REPO)
 | |
| 
 | |
| 
 | |
| 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):
 | |
|     # Python 2/3 compatibility
 | |
|     try:
 | |
|         read_input = raw_input
 | |
|     except NameError:
 | |
|         read_input = input
 | |
| 
 | |
|     while True:
 | |
|         query = read_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]
 | |
|     log_verbose('Running in %s: %s' % (cwd, ' '.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 svn(cwd, *cmd, **kwargs):
 | |
|     return shell(['svn'] + list(cmd), cwd=cwd, **kwargs)
 | |
| 
 | |
| 
 | |
| def program_exists(cmd):
 | |
|     if sys.platform == 'win32' and not cmd.endswith('.exe'):
 | |
|         cmd += '.exe'
 | |
|     for path in os.environ["PATH"].split(os.pathsep):
 | |
|         if os.access(os.path.join(path, cmd), os.X_OK):
 | |
|             return True
 | |
|     return False
 | |
| 
 | |
| 
 | |
| def get_fetch_url():
 | |
|     return 'https://{}'.format(GIT_URL)
 | |
| 
 | |
| 
 | |
| def get_push_url(user='', ssh=False):
 | |
| 
 | |
|     if ssh:
 | |
|         return 'ssh://git@{}'.format(GIT_URL)
 | |
| 
 | |
|     return 'https://{}'.format(GIT_URL)
 | |
| 
 | |
| 
 | |
| def get_revs_to_push(branch):
 | |
|     # Fetch the latest upstream to determine which commits will be pushed.
 | |
|     git('fetch', get_fetch_url(), branch)
 | |
| 
 | |
|     commits = git('rev-list', '--ancestry-path', 'FETCH_HEAD..HEAD').splitlines()
 | |
|     # Reverse the order so we commit the oldest commit first
 | |
|     commits.reverse()
 | |
|     return commits
 | |
| 
 | |
| 
 | |
| def git_push_one_rev(rev, dry_run, branch, ssh):
 | |
|     # Check if this a merge commit by counting the number of parent commits.
 | |
|     # More than 1 parent commmit means this is a merge.
 | |
|     num_parents = len(git('show', '--no-patch', '--format="%P"', rev).split())
 | |
| 
 | |
|     if num_parents > 1:
 | |
|         raise Exception("Merge commit detected, cannot push ", rev)
 | |
| 
 | |
|     if num_parents != 1:
 | |
|         raise Exception("Error detecting number of parents for ", rev)
 | |
| 
 | |
|     if dry_run:
 | |
|         print("[DryRun] Would push", rev)
 | |
|         return
 | |
| 
 | |
|     # Second push to actually push the commit
 | |
|     git('push', get_push_url(ssh=ssh), '{}:{}'.format(rev, branch), print_raw_stderr=True)
 | |
| 
 | |
| 
 | |
| def cmd_push(args):
 | |
|     '''Push changes to git:'''
 | |
|     dry_run = args.dry_run
 | |
| 
 | |
|     revs = get_revs_to_push(args.branch)
 | |
| 
 | |
|     if not revs:
 | |
|         die('Nothing to push')
 | |
| 
 | |
|     log('%sPushing %d commit%s:\n%s' %
 | |
|         ('[DryRun] ' if dry_run else '', len(revs),
 | |
|          's' if len(revs) != 1 else '',
 | |
|          '\n'.join('  ' + git('show', '--oneline', '--quiet', c)
 | |
|                    for c in revs)))
 | |
| 
 | |
|     # Ask confirmation if multiple commits are about to be pushed
 | |
|     if not args.force and len(revs) > 1:
 | |
|         if not ask_confirm("Are you sure you want to create %d commits?" % len(revs)):
 | |
|             die("Aborting")
 | |
| 
 | |
|     for r in revs:
 | |
|         git_push_one_rev(r, dry_run, args.branch, args.ssh)
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     if not program_exists('git'):
 | |
|         die('error: git-llvm needs git command, but git is not installed.')
 | |
| 
 | |
|     argv = sys.argv[1:]
 | |
|     p = argparse.ArgumentParser(
 | |
|         prog='git llvm', formatter_class=argparse.RawDescriptionHelpFormatter,
 | |
|         description=__doc__)
 | |
|     subcommands = p.add_subparsers(title='subcommands',
 | |
|                                    description='valid subcommands',
 | |
|                                    help='additional help')
 | |
|     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')
 | |
| 
 | |
|     parser_push = subcommands.add_parser(
 | |
|         'push', description=cmd_push.__doc__,
 | |
|         help='push changes back to the LLVM SVN repository')
 | |
|     parser_push.add_argument(
 | |
|         '-n',
 | |
|         '--dry-run',
 | |
|         dest='dry_run',
 | |
|         action='store_true',
 | |
|         help='Do everything other than commit to svn.  Leaves junk in the svn '
 | |
|         'repo, so probably will not work well if you try to commit more '
 | |
|         'than one rev.')
 | |
|     parser_push.add_argument(
 | |
|         '-s',
 | |
|         '--ssh',
 | |
|         dest='ssh',
 | |
|         action='store_true',
 | |
|         help='Use the SSH protocol for authentication, '
 | |
|         'instead of HTTPS with username and password.')
 | |
|     parser_push.add_argument(
 | |
|         '-f',
 | |
|         '--force',
 | |
|         action='store_true',
 | |
|         help='Do not ask for confirmation when pushing multiple commits.')
 | |
|     parser_push.add_argument(
 | |
|         'branch',
 | |
|         metavar='GIT_BRANCH',
 | |
|         type=str,
 | |
|         default='master',
 | |
|         nargs='?',
 | |
|         help="branch to push (default: everything not in the branch's "
 | |
|         'upstream)')
 | |
|     parser_push.set_defaults(func=cmd_push)
 | |
| 
 | |
|     args = p.parse_args(argv)
 | |
|     VERBOSE = args.verbose
 | |
|     QUIET = args.quiet
 | |
| 
 | |
|     # Python3 workaround, for when not arguments are provided.
 | |
|     # See https://bugs.python.org/issue16308
 | |
|     try:
 | |
|         func = args.func
 | |
|     except AttributeError:
 | |
|         # No arguments or subcommands were given.
 | |
|         parser.print_help()
 | |
|         parser.exit()
 | |
| 
 | |
|     # Dispatch to the right subcommand
 | |
|     args.func(args)
 |