mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-23 11:13:28 +01:00
eb1d5607fb
This was required for blocking merge commits, but now that we have branch protections, we don't need this.
325 lines
8.8 KiB
Python
Executable File
325 lines
8.8 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',
|
|
'llgo',
|
|
'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://{}'.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):
|
|
# 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(), '{}:{}'.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)
|
|
|
|
|
|
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(
|
|
'-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)
|