mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-26 05:06:06 +00:00

There is no need to support Python 2.7 anymore, Python 3.3+ has `subprocess.DEVNULL`. This is good practice and also prevents file handles from staying open unnecessarily. Also remove a couple unused or unneeded `__future__` imports.
234 lines
6.7 KiB
Python
Executable File
234 lines
6.7 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 shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
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 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)))
|
|
|
|
# Silence errors if requested.
|
|
err_pipe = subprocess.DEVNULL if ignore_errors else subprocess.PIPE
|
|
|
|
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)
|