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 # ==------------------------------------------------------------------------==#
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.
23 git-llvm push <upstream-branch>
25 This will push changes from the current HEAD to the branch <upstream-branch>.
29 from __future__
import print_function
39 assert sys
.version_info
>= (2, 7)
43 except AttributeError:
46 return iter(d
.items())
54 from shlex
import quote
57 from pipes
import quote
59 # It's *almost* a straightforward mapping from the monorepo to svn...
60 LLVM_MONOREPO_SVN_MAPPING
= {
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']}
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
):
105 print(*args
, **kwargs
)
108 def log_verbose(*args
, **kwargs
):
111 print(*args
, **kwargs
)
119 def ask_confirm(prompt
):
120 # Python 2/3 compatibility
122 read_input
= raw_input
127 query
= read_input('%s (y/N): ' % (prompt
))
128 if query
.lower() not in ['y','n', '']:
129 print('Expect y or n!')
131 return query
.lower() == 'y'
135 """Lazily create a /dev/null fd for use in shell()"""
137 if dev_null_fd
is None:
138 dev_null_fd
= open(os
.devnull
, 'w')
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
150 # Silence errors if requested.
151 err_pipe
= get_dev_null()
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())
169 stdout
= stdout
.rstrip('\r\n')
171 stdout
= stdout
.rstrip(b
'\r\n')
173 for l
in stdout
.splitlines():
174 log_verbose("STDOUT: %s" % l
)
176 err_msg
= '`%s` returned %s' % (' '.join(quoted_cmd
), p
.returncode
)
179 eprint(stderr
.rstrip())
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'):
196 for path
in os
.environ
["PATH"].split(os
.pathsep
):
197 if os
.access(os
.path
.join(path
, cmd
), os
.X_OK
):
203 return 'https://{}'.format(GIT_URL
)
206 def get_push_url(user
='', ssh
=False):
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
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())
230 raise Exception("Merge commit detected, cannot push ", rev
)
233 raise Exception("Error detecting number of parents for ", rev
)
236 print("[DryRun] Would push", rev
)
239 # Second push to actually push the commit
240 git('push', get_push_url(ssh
=ssh
), '{}:{}'.format(rev
, branch
), print_raw_stderr
=True)
244 '''Push changes to git:'''
245 dry_run
= args
.dry_run
247 revs
= get_revs_to_push(args
.branch
)
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
)
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
)):
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.')
272 p
= argparse
.ArgumentParser(
273 prog
='git llvm', formatter_class
=argparse
.RawDescriptionHelpFormatter
,
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(
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 '
295 parser_push
.add_argument(
300 help='Use the SSH protocol for authentication, '
301 'instead of HTTPS with username and password.')
302 parser_push
.add_argument(
306 help='Do not ask for confirmation when pushing multiple commits.')
307 parser_push
.add_argument(
309 metavar
='GIT_BRANCH',
313 help="branch to push (default: everything not in the branch's "
315 parser_push
.set_defaults(func
=cmd_push
)
317 args
= p
.parse_args(argv
)
318 VERBOSE
= args
.verbose
321 # Python3 workaround, for when not arguments are provided.
322 # See https://bugs.python.org/issue16308
325 except AttributeError:
326 # No arguments or subcommands were given.
330 # Dispatch to the right subcommand