mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-23 03:02:36 +01:00
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)
|