3 # ======- pre-push - LLVM Git Help Integration ---------*- python -*--========#
5 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6 # See https://llvm.org/LICENSE.txt for license information.
7 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
9 # ==------------------------------------------------------------------------==#
12 pre-push git hook integration
13 =============================
15 This script is intended to be setup as a pre-push hook, from the root of the
18 ln -sf ../../llvm/utils/git/pre-push.py .git/hooks/pre-push
22 The pre-push hook runs during git push, after the remote refs have been
23 updated but before any objects have been transferred. It receives the name
24 and location of the remote as parameters, and a list of to-be-updated refs
25 through stdin. You can use it to validate a set of ref updates before a push
26 occurs (a non-zero exit code will abort the push).
34 from shlex
import quote
39 z40
= "0000000000000000000000000000000000000000"
42 def eprint(*args
, **kwargs
):
43 print(*args
, file=sys
.stderr
, **kwargs
)
46 def log(*args
, **kwargs
):
49 print(*args
, **kwargs
)
52 def log_verbose(*args
, **kwargs
):
55 print(*args
, **kwargs
)
63 def ask_confirm(prompt
):
65 query
= input("%s (y/N): " % (prompt
))
66 if query
.lower() not in ["y", "n", ""]:
67 print("Expect y or n!")
69 return query
.lower() == "y"
80 print_raw_stderr
=False,
82 # Escape args when logging for easy repro.
83 quoted_cmd
= [quote(arg
) for arg
in cmd
]
86 cwd_msg
= " in %s" % cwd
87 log_verbose("Running%s: %s" % (cwd_msg
, " ".join(quoted_cmd
)))
89 # Silence errors if requested.
90 err_pipe
= subprocess
.DEVNULL
if ignore_errors
else subprocess
.PIPE
96 stdout
=subprocess
.PIPE
,
98 stdin
=subprocess
.PIPE
,
99 universal_newlines
=text
,
101 stdout
, stderr
= p
.communicate(input=stdin
)
102 elapsed
= time
.time() - start
104 log_verbose("Command took %0.1fs" % elapsed
)
106 if p
.returncode
== 0 or ignore_errors
:
107 if stderr
and not ignore_errors
:
108 if not print_raw_stderr
:
109 eprint("`%s` printed to stderr:" % " ".join(quoted_cmd
))
110 eprint(stderr
.rstrip())
113 stdout
= stdout
.rstrip("\r\n")
115 stdout
= stdout
.rstrip(b
"\r\n")
117 for l
in stdout
.splitlines():
118 log_verbose("STDOUT: %s" % l
)
120 err_msg
= "`%s` returned %s" % (" ".join(quoted_cmd
), p
.returncode
)
123 eprint(stderr
.rstrip())
126 raise RuntimeError(err_msg
)
129 def git(*cmd
, **kwargs
):
130 return shell(["git"] + list(cmd
), **kwargs
)
133 def get_revs_to_push(range):
134 commits
= git("rev-list", range).splitlines()
135 # Reverse the order so we print the oldest commit first
140 def handle_push(args
, local_ref
, local_sha
, remote_ref
, remote_sha
):
141 """Check a single push request (which can include multiple revisions)"""
143 "Handle push, reproduce with "
144 "`echo %s %s %s %s | pre-push.py %s %s"
145 % (local_ref
, local_sha
, remote_ref
, remote_sha
, args
.remote
, args
.url
)
147 # Handle request to delete
150 'Are you sure you want to delete "%s" on remote "%s"?'
151 % (remote_ref
, args
.url
)
157 if remote_sha
== z40
:
159 'Are you sure you want to push a new branch/tag "%s" on remote "%s"?'
160 % (remote_ref
, args
.url
)
166 # Update to existing branch, examine new commits
167 range = "%s..%s" % (remote_sha
, local_sha
)
168 # Check that the remote commit exists, otherwise let git proceed
169 if "commit" not in git("cat-file", "-t", remote_sha
, ignore_errors
=True):
172 revs
= get_revs_to_push(range)
174 # This can happen if someone is force pushing an older revision to a branch
177 # Print the revision about to be pushed commits
178 print('Pushing to "%s" on remote "%s"' % (remote_ref
, args
.url
))
180 print(" - " + git("show", "--oneline", "--quiet", sha
))
183 if not ask_confirm("Are you sure you want to push %d commits?" % len(revs
)):
187 msg
= git("log", "--format=%B", "-n1", sha
)
188 if "Differential Revision" not in msg
:
190 for line
in msg
.splitlines():
191 for tag
in ["Summary", "Reviewers", "Subscribers", "Tags"]:
192 if line
.startswith(tag
+ ":"):
194 'Please remove arcanist tags from the commit message (found "%s" tag in %s)'
198 eprint("Try running: llvm/utils/git/arcfilter.sh")
199 die('Aborting (force push by adding "--no-verify")')
204 if __name__
== "__main__":
205 if not shutil
.which("git"):
206 die("error: cannot find git command")
209 p
= argparse
.ArgumentParser(
211 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
214 verbosity_group
= p
.add_mutually_exclusive_group()
215 verbosity_group
.add_argument(
216 "-q", "--quiet", action
="store_true", help="print less information"
218 verbosity_group
.add_argument(
219 "-v", "--verbose", action
="store_true", help="print more information"
222 p
.add_argument("remote", type=str, help="Name of the remote")
223 p
.add_argument("url", type=str, help="URL for the remote")
225 args
= p
.parse_args(argv
)
226 VERBOSE
= args
.verbose
229 lines
= sys
.stdin
.readlines()
230 sys
.stdin
= open("/dev/tty", "r")
232 local_ref
, local_sha
, remote_ref
, remote_sha
= line
.split()
233 handle_push(args
, local_ref
, local_sha
, remote_ref
, remote_sha
)