Clang] Fix expansion of response files in -Wp after integrated-cc1 change
[llvm-project.git] / llvm / utils / git-svn / git-llvm
blobbc60e02aae39c225c9323e48975dfd146a67c082
1 #!/usr/bin/env python
3 # ======- git-llvm - 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 git-llvm integration
13 ====================
15 This file provides integration for git.
17 The git llvm push sub-command can be used to push changes to GitHub. It is
18 designed to be a thin wrapper around git, and its main purpose is to
19 detect and prevent merge commits from being pushed to the main repository.
21 Usage:
23 git-llvm push <upstream-branch>
25 This will push changes from the current HEAD to the branch <upstream-branch>.
27 """
29 from __future__ import print_function
30 import argparse
31 import collections
32 import os
33 import re
34 import shutil
35 import subprocess
36 import sys
37 import time
38 import getpass
39 assert sys.version_info >= (2, 7)
41 try:
42 dict.iteritems
43 except AttributeError:
44 # Python 3
45 def iteritems(d):
46 return iter(d.items())
47 else:
48 # Python 2
49 def iteritems(d):
50 return d.iteritems()
52 try:
53 # Python 3
54 from shlex import quote
55 except ImportError:
56 # Python 2
57 from pipes import quote
59 # It's *almost* a straightforward mapping from the monorepo to svn...
60 LLVM_MONOREPO_SVN_MAPPING = {
61 d: (d + '/trunk')
62 for d in [
63 'clang-tools-extra',
64 'compiler-rt',
65 'debuginfo-tests',
66 'dragonegg',
67 'klee',
68 'libc',
69 'libclc',
70 'libcxx',
71 'libcxxabi',
72 'libunwind',
73 'lld',
74 'lldb',
75 'llgo',
76 'llvm',
77 'openmp',
78 'parallel-libs',
79 'polly',
80 'pstl',
83 LLVM_MONOREPO_SVN_MAPPING.update({'clang': 'cfe/trunk'})
84 LLVM_MONOREPO_SVN_MAPPING.update({'': 'monorepo-root/trunk'})
86 SPLIT_REPO_NAMES = {'llvm-' + d: d + '/trunk'
87 for d in ['www', 'zorg', 'test-suite', 'lnt']}
89 VERBOSE = False
90 QUIET = False
91 dev_null_fd = None
93 GIT_ORG = 'llvm'
94 GIT_REPO = 'llvm-project'
95 GIT_URL = 'github.com/{}/{}.git'.format(GIT_ORG, GIT_REPO)
98 def eprint(*args, **kwargs):
99 print(*args, file=sys.stderr, **kwargs)
102 def log(*args, **kwargs):
103 if QUIET:
104 return
105 print(*args, **kwargs)
108 def log_verbose(*args, **kwargs):
109 if not VERBOSE:
110 return
111 print(*args, **kwargs)
114 def die(msg):
115 eprint(msg)
116 sys.exit(1)
119 def ask_confirm(prompt):
120 # Python 2/3 compatibility
121 try:
122 read_input = raw_input
123 except NameError:
124 read_input = input
126 while True:
127 query = read_input('%s (y/N): ' % (prompt))
128 if query.lower() not in ['y','n', '']:
129 print('Expect y or n!')
130 continue
131 return query.lower() == 'y'
134 def get_dev_null():
135 """Lazily create a /dev/null fd for use in shell()"""
136 global dev_null_fd
137 if dev_null_fd is None:
138 dev_null_fd = open(os.devnull, 'w')
139 return dev_null_fd
142 def shell(cmd, strip=True, cwd=None, stdin=None, die_on_failure=True,
143 ignore_errors=False, text=True, print_raw_stderr=False):
144 # Escape args when logging for easy repro.
145 quoted_cmd = [quote(arg) for arg in cmd]
146 log_verbose('Running in %s: %s' % (cwd, ' '.join(quoted_cmd)))
148 err_pipe = subprocess.PIPE
149 if ignore_errors:
150 # Silence errors if requested.
151 err_pipe = get_dev_null()
153 start = time.time()
154 p = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=err_pipe,
155 stdin=subprocess.PIPE,
156 universal_newlines=text)
157 stdout, stderr = p.communicate(input=stdin)
158 elapsed = time.time() - start
160 log_verbose('Command took %0.1fs' % elapsed)
162 if p.returncode == 0 or ignore_errors:
163 if stderr and not ignore_errors:
164 if not print_raw_stderr:
165 eprint('`%s` printed to stderr:' % ' '.join(quoted_cmd))
166 eprint(stderr.rstrip())
167 if strip:
168 if text:
169 stdout = stdout.rstrip('\r\n')
170 else:
171 stdout = stdout.rstrip(b'\r\n')
172 if VERBOSE:
173 for l in stdout.splitlines():
174 log_verbose("STDOUT: %s" % l)
175 return stdout
176 err_msg = '`%s` returned %s' % (' '.join(quoted_cmd), p.returncode)
177 eprint(err_msg)
178 if stderr:
179 eprint(stderr.rstrip())
180 if die_on_failure:
181 sys.exit(2)
182 raise RuntimeError(err_msg)
185 def git(*cmd, **kwargs):
186 return shell(['git'] + list(cmd), **kwargs)
189 def svn(cwd, *cmd, **kwargs):
190 return shell(['svn'] + list(cmd), cwd=cwd, **kwargs)
193 def program_exists(cmd):
194 if sys.platform == 'win32' and not cmd.endswith('.exe'):
195 cmd += '.exe'
196 for path in os.environ["PATH"].split(os.pathsep):
197 if os.access(os.path.join(path, cmd), os.X_OK):
198 return True
199 return False
202 def get_fetch_url():
203 return 'https://{}'.format(GIT_URL)
206 def get_push_url(user='', ssh=False):
208 if ssh:
209 return 'ssh://git@{}'.format(GIT_URL)
211 return 'https://{}'.format(GIT_URL)
214 def get_revs_to_push(branch):
215 # Fetch the latest upstream to determine which commits will be pushed.
216 git('fetch', get_fetch_url(), branch)
218 commits = git('rev-list', '--ancestry-path', 'FETCH_HEAD..HEAD').splitlines()
219 # Reverse the order so we commit the oldest commit first
220 commits.reverse()
221 return commits
224 def git_push_one_rev(rev, dry_run, branch, ssh):
225 # Check if this a merge commit by counting the number of parent commits.
226 # More than 1 parent commmit means this is a merge.
227 num_parents = len(git('show', '--no-patch', '--format="%P"', rev).split())
229 if num_parents > 1:
230 raise Exception("Merge commit detected, cannot push ", rev)
232 if num_parents != 1:
233 raise Exception("Error detecting number of parents for ", rev)
235 if dry_run:
236 print("[DryRun] Would push", rev)
237 return
239 # Second push to actually push the commit
240 git('push', get_push_url(ssh=ssh), '{}:{}'.format(rev, branch), print_raw_stderr=True)
243 def cmd_push(args):
244 '''Push changes to git:'''
245 dry_run = args.dry_run
247 revs = get_revs_to_push(args.branch)
249 if not revs:
250 die('Nothing to push')
252 log('%sPushing %d commit%s:\n%s' %
253 ('[DryRun] ' if dry_run else '', len(revs),
254 's' if len(revs) != 1 else '',
255 '\n'.join(' ' + git('show', '--oneline', '--quiet', c)
256 for c in revs)))
258 # Ask confirmation if multiple commits are about to be pushed
259 if not args.force and len(revs) > 1:
260 if not ask_confirm("Are you sure you want to create %d commits?" % len(revs)):
261 die("Aborting")
263 for r in revs:
264 git_push_one_rev(r, dry_run, args.branch, args.ssh)
267 if __name__ == '__main__':
268 if not program_exists('git'):
269 die('error: git-llvm needs git command, but git is not installed.')
271 argv = sys.argv[1:]
272 p = argparse.ArgumentParser(
273 prog='git llvm', formatter_class=argparse.RawDescriptionHelpFormatter,
274 description=__doc__)
275 subcommands = p.add_subparsers(title='subcommands',
276 description='valid subcommands',
277 help='additional help')
278 verbosity_group = p.add_mutually_exclusive_group()
279 verbosity_group.add_argument('-q', '--quiet', action='store_true',
280 help='print less information')
281 verbosity_group.add_argument('-v', '--verbose', action='store_true',
282 help='print more information')
284 parser_push = subcommands.add_parser(
285 'push', description=cmd_push.__doc__,
286 help='push changes back to the LLVM SVN repository')
287 parser_push.add_argument(
288 '-n',
289 '--dry-run',
290 dest='dry_run',
291 action='store_true',
292 help='Do everything other than commit to svn. Leaves junk in the svn '
293 'repo, so probably will not work well if you try to commit more '
294 'than one rev.')
295 parser_push.add_argument(
296 '-s',
297 '--ssh',
298 dest='ssh',
299 action='store_true',
300 help='Use the SSH protocol for authentication, '
301 'instead of HTTPS with username and password.')
302 parser_push.add_argument(
303 '-f',
304 '--force',
305 action='store_true',
306 help='Do not ask for confirmation when pushing multiple commits.')
307 parser_push.add_argument(
308 'branch',
309 metavar='GIT_BRANCH',
310 type=str,
311 default='master',
312 nargs='?',
313 help="branch to push (default: everything not in the branch's "
314 'upstream)')
315 parser_push.set_defaults(func=cmd_push)
317 args = p.parse_args(argv)
318 VERBOSE = args.verbose
319 QUIET = args.quiet
321 # Python3 workaround, for when not arguments are provided.
322 # See https://bugs.python.org/issue16308
323 try:
324 func = args.func
325 except AttributeError:
326 # No arguments or subcommands were given.
327 parser.print_help()
328 parser.exit()
330 # Dispatch to the right subcommand
331 args.func(args)