Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / llvm / utils / git / pre-push.py
blobd7ae3767d2923d396b64f7ab2cc9190df392c74c
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 os
31 import shutil
32 import subprocess
33 import sys
34 import time
35 from shlex import quote
37 VERBOSE = False
38 QUIET = False
39 dev_null_fd = None
40 z40 = "0000000000000000000000000000000000000000"
43 def eprint(*args, **kwargs):
44 print(*args, file=sys.stderr, **kwargs)
47 def log(*args, **kwargs):
48 if QUIET:
49 return
50 print(*args, **kwargs)
53 def log_verbose(*args, **kwargs):
54 if not VERBOSE:
55 return
56 print(*args, **kwargs)
59 def die(msg):
60 eprint(msg)
61 sys.exit(1)
64 def ask_confirm(prompt):
65 while True:
66 query = input("%s (y/N): " % (prompt))
67 if query.lower() not in ["y", "n", ""]:
68 print("Expect y or n!")
69 continue
70 return query.lower() == "y"
73 def get_dev_null():
74 """Lazily create a /dev/null fd for use in shell()"""
75 global dev_null_fd
76 if dev_null_fd is None:
77 dev_null_fd = open(os.devnull, "w")
78 return dev_null_fd
81 def shell(
82 cmd,
83 strip=True,
84 cwd=None,
85 stdin=None,
86 die_on_failure=True,
87 ignore_errors=False,
88 text=True,
89 print_raw_stderr=False,
91 # Escape args when logging for easy repro.
92 quoted_cmd = [quote(arg) for arg in cmd]
93 cwd_msg = ""
94 if cwd:
95 cwd_msg = " in %s" % cwd
96 log_verbose("Running%s: %s" % (cwd_msg, " ".join(quoted_cmd)))
98 err_pipe = subprocess.PIPE
99 if ignore_errors:
100 # Silence errors if requested.
101 err_pipe = get_dev_null()
103 start = time.time()
104 p = subprocess.Popen(
105 cmd,
106 cwd=cwd,
107 stdout=subprocess.PIPE,
108 stderr=err_pipe,
109 stdin=subprocess.PIPE,
110 universal_newlines=text,
112 stdout, stderr = p.communicate(input=stdin)
113 elapsed = time.time() - start
115 log_verbose("Command took %0.1fs" % elapsed)
117 if p.returncode == 0 or ignore_errors:
118 if stderr and not ignore_errors:
119 if not print_raw_stderr:
120 eprint("`%s` printed to stderr:" % " ".join(quoted_cmd))
121 eprint(stderr.rstrip())
122 if strip:
123 if text:
124 stdout = stdout.rstrip("\r\n")
125 else:
126 stdout = stdout.rstrip(b"\r\n")
127 if VERBOSE:
128 for l in stdout.splitlines():
129 log_verbose("STDOUT: %s" % l)
130 return stdout
131 err_msg = "`%s` returned %s" % (" ".join(quoted_cmd), p.returncode)
132 eprint(err_msg)
133 if stderr:
134 eprint(stderr.rstrip())
135 if die_on_failure:
136 sys.exit(2)
137 raise RuntimeError(err_msg)
140 def git(*cmd, **kwargs):
141 return shell(["git"] + list(cmd), **kwargs)
144 def get_revs_to_push(range):
145 commits = git("rev-list", range).splitlines()
146 # Reverse the order so we print the oldest commit first
147 commits.reverse()
148 return commits
151 def handle_push(args, local_ref, local_sha, remote_ref, remote_sha):
152 """Check a single push request (which can include multiple revisions)"""
153 log_verbose(
154 "Handle push, reproduce with "
155 "`echo %s %s %s %s | pre-push.py %s %s"
156 % (local_ref, local_sha, remote_ref, remote_sha, args.remote, args.url)
158 # Handle request to delete
159 if local_sha == z40:
160 if not ask_confirm(
161 'Are you sure you want to delete "%s" on remote "%s"?'
162 % (remote_ref, args.url)
164 die("Aborting")
165 return
167 # Push a new branch
168 if remote_sha == z40:
169 if not ask_confirm(
170 'Are you sure you want to push a new branch/tag "%s" on remote "%s"?'
171 % (remote_ref, args.url)
173 die("Aborting")
174 range = local_sha
175 return
176 else:
177 # Update to existing branch, examine new commits
178 range = "%s..%s" % (remote_sha, local_sha)
179 # Check that the remote commit exists, otherwise let git proceed
180 if "commit" not in git("cat-file", "-t", remote_sha, ignore_errors=True):
181 return
183 revs = get_revs_to_push(range)
184 if not revs:
185 # This can happen if someone is force pushing an older revision to a branch
186 return
188 # Print the revision about to be pushed commits
189 print('Pushing to "%s" on remote "%s"' % (remote_ref, args.url))
190 for sha in revs:
191 print(" - " + git("show", "--oneline", "--quiet", sha))
193 if len(revs) > 1:
194 if not ask_confirm("Are you sure you want to push %d commits?" % len(revs)):
195 die("Aborting")
197 for sha in revs:
198 msg = git("log", "--format=%B", "-n1", sha)
199 if "Differential Revision" not in msg:
200 continue
201 for line in msg.splitlines():
202 for tag in ["Summary", "Reviewers", "Subscribers", "Tags"]:
203 if line.startswith(tag + ":"):
204 eprint(
205 'Please remove arcanist tags from the commit message (found "%s" tag in %s)'
206 % (tag, sha[:12])
208 if len(revs) == 1:
209 eprint("Try running: llvm/utils/git/arcfilter.sh")
210 die('Aborting (force push by adding "--no-verify")')
212 return
215 if __name__ == "__main__":
216 if not shutil.which("git"):
217 die("error: cannot find git command")
219 argv = sys.argv[1:]
220 p = argparse.ArgumentParser(
221 prog="pre-push",
222 formatter_class=argparse.RawDescriptionHelpFormatter,
223 description=__doc__,
225 verbosity_group = p.add_mutually_exclusive_group()
226 verbosity_group.add_argument(
227 "-q", "--quiet", action="store_true", help="print less information"
229 verbosity_group.add_argument(
230 "-v", "--verbose", action="store_true", help="print more information"
233 p.add_argument("remote", type=str, help="Name of the remote")
234 p.add_argument("url", type=str, help="URL for the remote")
236 args = p.parse_args(argv)
237 VERBOSE = args.verbose
238 QUIET = args.quiet
240 lines = sys.stdin.readlines()
241 sys.stdin = open("/dev/tty", "r")
242 for line in lines:
243 local_ref, local_sha, remote_ref, remote_sha = line.split()
244 handle_push(args, local_ref, local_sha, remote_ref, remote_sha)