[clang-format] Add `TT_CompoundRequirementLBrace` for better annotation (#121539)
[llvm-project.git] / llvm / utils / git / pre-push.py
blobdfa009dd1a6f62afe573ad438f005b089b2d87e3
1 #!/usr/bin/env python3
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 # ==------------------------------------------------------------------------==#
11 """
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
16 repo run:
18 ln -sf ../../llvm/utils/git/pre-push.py .git/hooks/pre-push
20 From the git doc:
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).
27 """
29 import argparse
30 import shutil
31 import subprocess
32 import sys
33 import time
34 from shlex import quote
36 VERBOSE = False
37 QUIET = False
38 dev_null_fd = None
39 z40 = "0000000000000000000000000000000000000000"
42 def eprint(*args, **kwargs):
43 print(*args, file=sys.stderr, **kwargs)
46 def log(*args, **kwargs):
47 if QUIET:
48 return
49 print(*args, **kwargs)
52 def log_verbose(*args, **kwargs):
53 if not VERBOSE:
54 return
55 print(*args, **kwargs)
58 def die(msg):
59 eprint(msg)
60 sys.exit(1)
63 def ask_confirm(prompt):
64 while True:
65 query = input("%s (y/N): " % (prompt))
66 if query.lower() not in ["y", "n", ""]:
67 print("Expect y or n!")
68 continue
69 return query.lower() == "y"
72 def shell(
73 cmd,
74 strip=True,
75 cwd=None,
76 stdin=None,
77 die_on_failure=True,
78 ignore_errors=False,
79 text=True,
80 print_raw_stderr=False,
82 # Escape args when logging for easy repro.
83 quoted_cmd = [quote(arg) for arg in cmd]
84 cwd_msg = ""
85 if cwd:
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
92 start = time.time()
93 p = subprocess.Popen(
94 cmd,
95 cwd=cwd,
96 stdout=subprocess.PIPE,
97 stderr=err_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())
111 if strip:
112 if text:
113 stdout = stdout.rstrip("\r\n")
114 else:
115 stdout = stdout.rstrip(b"\r\n")
116 if VERBOSE:
117 for l in stdout.splitlines():
118 log_verbose("STDOUT: %s" % l)
119 return stdout
120 err_msg = "`%s` returned %s" % (" ".join(quoted_cmd), p.returncode)
121 eprint(err_msg)
122 if stderr:
123 eprint(stderr.rstrip())
124 if die_on_failure:
125 sys.exit(2)
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
136 commits.reverse()
137 return commits
140 def handle_push(args, local_ref, local_sha, remote_ref, remote_sha):
141 """Check a single push request (which can include multiple revisions)"""
142 log_verbose(
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
148 if local_sha == z40:
149 if not ask_confirm(
150 'Are you sure you want to delete "%s" on remote "%s"?'
151 % (remote_ref, args.url)
153 die("Aborting")
154 return
156 # Push a new branch
157 if remote_sha == z40:
158 if not ask_confirm(
159 'Are you sure you want to push a new branch/tag "%s" on remote "%s"?'
160 % (remote_ref, args.url)
162 die("Aborting")
163 range = local_sha
164 return
165 else:
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):
170 return
172 revs = get_revs_to_push(range)
173 if not revs:
174 # This can happen if someone is force pushing an older revision to a branch
175 return
177 # Print the revision about to be pushed commits
178 print('Pushing to "%s" on remote "%s"' % (remote_ref, args.url))
179 for sha in revs:
180 print(" - " + git("show", "--oneline", "--quiet", sha))
182 if len(revs) > 1:
183 if not ask_confirm("Are you sure you want to push %d commits?" % len(revs)):
184 die("Aborting")
186 for sha in revs:
187 msg = git("log", "--format=%B", "-n1", sha)
188 if "Differential Revision" not in msg:
189 continue
190 for line in msg.splitlines():
191 for tag in ["Summary", "Reviewers", "Subscribers", "Tags"]:
192 if line.startswith(tag + ":"):
193 eprint(
194 'Please remove arcanist tags from the commit message (found "%s" tag in %s)'
195 % (tag, sha[:12])
197 if len(revs) == 1:
198 eprint("Try running: llvm/utils/git/arcfilter.sh")
199 die('Aborting (force push by adding "--no-verify")')
201 return
204 if __name__ == "__main__":
205 if not shutil.which("git"):
206 die("error: cannot find git command")
208 argv = sys.argv[1:]
209 p = argparse.ArgumentParser(
210 prog="pre-push",
211 formatter_class=argparse.RawDescriptionHelpFormatter,
212 description=__doc__,
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
227 QUIET = args.quiet
229 lines = sys.stdin.readlines()
230 sys.stdin = open("/dev/tty", "r")
231 for line in lines:
232 local_ref, local_sha, remote_ref, remote_sha = line.split()
233 handle_push(args, local_ref, local_sha, remote_ref, remote_sha)