blob: b5289f772cffda91da87a0801357583adf1b6db1 [file] [log] [blame]
iannucci@chromium.org405b87e2015-11-12 18:08:34 +00001#!/usr/bin/env python
miket@chromium.org183df1a2012-01-04 19:44:55 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.org725f1c32011-04-01 20:24:54 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00006# Copyright (C) 2008 Evan Martin <martine@danga.com>
7
maruel@chromium.org725f1c32011-04-01 20:24:54 +00008"""A git-command for integrating reviews on Rietveld."""
9
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +000010from distutils.version import LooseVersion
calamity@chromium.orgffde55c2015-03-12 00:44:17 +000011from multiprocessing.pool import ThreadPool
thakis@chromium.org3421c992014-11-02 02:20:32 +000012import base64
rmistry@google.com2dd99862015-06-22 12:22:18 +000013import collections
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +000014import glob
sheyang@google.com6ebaf782015-05-12 19:17:54 +000015import httplib
maruel@chromium.org4f6852c2012-04-20 20:39:20 +000016import json
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000017import logging
18import optparse
19import os
maruel@chromium.org1033efd2013-07-23 23:25:09 +000020import Queue
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000021import re
ukai@chromium.org78c4b982012-02-14 02:20:26 +000022import stat
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000023import sys
bauerb@chromium.org27386dd2015-02-16 10:45:39 +000024import tempfile
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000025import textwrap
sheyang@google.com6ebaf782015-05-12 19:17:54 +000026import time
27import traceback
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000028import urllib2
maruel@chromium.org967c0a82013-06-17 22:52:24 +000029import urlparse
thestig@chromium.org00858c82013-12-02 23:08:03 +000030import webbrowser
thakis@chromium.org3421c992014-11-02 02:20:32 +000031import zlib
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000032
33try:
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000034 import readline # pylint: disable=F0401,W0611
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000035except ImportError:
36 pass
37
maruel@chromium.org2e23ce32013-05-07 12:42:28 +000038from third_party import colorama
sheyang@google.com6ebaf782015-05-12 19:17:54 +000039from third_party import httplib2
maruel@chromium.org2a74d372011-03-29 19:05:50 +000040from third_party import upload
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +000041import auth
hinoka@chromium.orgfeb9e2a2015-09-25 19:11:09 +000042from luci_hacks import trigger_luci_job as luci_trigger
maruel@chromium.org2a74d372011-03-29 19:05:50 +000043import breakpad # pylint: disable=W0611
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +000044import clang_format
tandrii@chromium.org71184c02016-01-13 15:18:44 +000045import commit_queue
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +000046import dart_format
maruel@chromium.org6f09cd92011-04-01 16:38:12 +000047import fix_encoding
maruel@chromium.org0e0436a2011-10-25 13:32:41 +000048import gclient_utils
iannucci@chromium.org9e849272014-04-04 00:31:55 +000049import git_common
mmoss@chromium.orgf0e41522015-06-10 19:52:01 +000050from git_footers import get_footer_svn_id
piman@chromium.org336f9122014-09-04 02:16:55 +000051import owners
iannucci@chromium.org9e849272014-04-04 00:31:55 +000052import owners_finder
maruel@chromium.org2a74d372011-03-29 19:05:50 +000053import presubmit_support
maruel@chromium.orgcab38e92011-04-09 00:30:51 +000054import rietveld
maruel@chromium.org2a74d372011-03-29 19:05:50 +000055import scm
maruel@chromium.org0633fb42013-08-16 20:06:14 +000056import subcommand
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000057import subprocess2
maruel@chromium.org2a74d372011-03-29 19:05:50 +000058import watchlists
59
maruel@chromium.org0633fb42013-08-16 20:06:14 +000060__version__ = '1.0'
maruel@chromium.org2a74d372011-03-29 19:05:50 +000061
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +000062DEFAULT_SERVER = 'https://codereview.appspot.com'
maruel@chromium.org0ba7f962011-01-11 22:13:58 +000063POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000064DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +000065GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit'
sivachandra@chromium.orgaebe87f2012-10-22 20:34:21 +000066CHANGE_ID = 'Change-Id:'
rmistry@google.comc68112d2015-03-03 12:48:06 +000067REFS_THAT_ALIAS_TO_OTHER_REFS = {
68 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master',
69 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master',
70}
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000071
thestig@chromium.org44202a22014-03-11 19:22:18 +000072# Valid extensions for files we want to lint.
73DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
74DEFAULT_LINT_IGNORE_REGEX = r"$^"
75
maruel@chromium.org2e23ce32013-05-07 12:42:28 +000076# Shortcut since it quickly becomes redundant.
77Fore = colorama.Fore
maruel@chromium.org90541732011-04-01 17:54:18 +000078
maruel@chromium.orgddd59412011-11-30 14:20:38 +000079# Initialized in main()
80settings = None
81
82
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000083def DieWithError(message):
dpranke@chromium.org970c5222011-03-12 00:32:24 +000084 print >> sys.stderr, message
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000085 sys.exit(1)
86
87
thestig@chromium.org8b0553c2014-02-11 00:33:37 +000088def GetNoGitPagerEnv():
89 env = os.environ.copy()
90 # 'cat' is a magical git string that disables pagers on all platforms.
91 env['GIT_PAGER'] = 'cat'
92 return env
93
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +000094
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000095def RunCommand(args, error_ok=False, error_message=None, **kwargs):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000096 try:
maruel@chromium.org373af802012-05-25 21:07:33 +000097 return subprocess2.check_output(args, shell=False, **kwargs)
maruel@chromium.org78936cb2013-04-11 00:17:52 +000098 except subprocess2.CalledProcessError as e:
99 logging.debug('Failed running %s', args)
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000100 if not error_ok:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000101 DieWithError(
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000102 'Command "%s" failed.\n%s' % (
103 ' '.join(args), error_message or e.stdout or ''))
104 return e.stdout
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000105
106
107def RunGit(args, **kwargs):
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000108 """Returns stdout."""
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000109 return RunCommand(['git'] + args, **kwargs)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000110
111
enne@chromium.org3b7e15c2014-01-21 17:44:47 +0000112def RunGitWithCode(args, suppress_stderr=False):
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000113 """Returns return code and stdout."""
szager@chromium.org9bb85e22012-06-13 20:28:23 +0000114 try:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +0000115 if suppress_stderr:
116 stderr = subprocess2.VOID
117 else:
118 stderr = sys.stderr
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000119 out, code = subprocess2.communicate(['git'] + args,
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000120 env=GetNoGitPagerEnv(),
enne@chromium.org3b7e15c2014-01-21 17:44:47 +0000121 stdout=subprocess2.PIPE,
122 stderr=stderr)
szager@chromium.org9bb85e22012-06-13 20:28:23 +0000123 return code, out[0]
124 except ValueError:
125 # When the subprocess fails, it returns None. That triggers a ValueError
126 # when trying to unpack the return value into (out, code).
127 return 1, ''
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000128
129
bauerb@chromium.org27386dd2015-02-16 10:45:39 +0000130def RunGitSilent(args):
131 """Returns stdout, suppresses stderr and ingores the return code."""
132 return RunGitWithCode(args, suppress_stderr=True)[1]
133
134
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000135def IsGitVersionAtLeast(min_version):
ilevy@chromium.orgcc56ee42013-07-10 22:16:29 +0000136 prefix = 'git version '
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000137 version = RunGit(['--version']).strip()
ilevy@chromium.orgcc56ee42013-07-10 22:16:29 +0000138 return (version.startswith(prefix) and
139 LooseVersion(version[len(prefix):]) >= LooseVersion(min_version))
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000140
141
pgervais@chromium.org8ba38ff2015-06-11 21:41:25 +0000142def BranchExists(branch):
143 """Return True if specified branch exists."""
144 code, _ = RunGitWithCode(['rev-parse', '--verify', branch],
145 suppress_stderr=True)
146 return not code
147
148
maruel@chromium.org90541732011-04-01 17:54:18 +0000149def ask_for_data(prompt):
150 try:
151 return raw_input(prompt)
152 except KeyboardInterrupt:
153 # Hide the exception.
154 sys.exit(1)
155
156
iannucci@chromium.org79540052012-10-19 23:15:26 +0000157def git_set_branch_value(key, value):
158 branch = Changelist().GetBranch()
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +0000159 if not branch:
160 return
161
162 cmd = ['config']
163 if isinstance(value, int):
164 cmd.append('--int')
165 git_key = 'branch.%s.%s' % (branch, key)
166 RunGit(cmd + [git_key, str(value)])
iannucci@chromium.org79540052012-10-19 23:15:26 +0000167
168
169def git_get_branch_default(key, default):
170 branch = Changelist().GetBranch()
171 if branch:
172 git_key = 'branch.%s.%s' % (branch, key)
173 (_, stdout) = RunGitWithCode(['config', '--int', '--get', git_key])
174 try:
175 return int(stdout.strip())
176 except ValueError:
177 pass
178 return default
179
180
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000181def add_git_similarity(parser):
182 parser.add_option(
iannucci@chromium.org79540052012-10-19 23:15:26 +0000183 '--similarity', metavar='SIM', type='int', action='store',
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000184 help='Sets the percentage that a pair of files need to match in order to'
185 ' be considered copies (default 50)')
iannucci@chromium.org79540052012-10-19 23:15:26 +0000186 parser.add_option(
187 '--find-copies', action='store_true',
188 help='Allows git to look for copies.')
189 parser.add_option(
190 '--no-find-copies', action='store_false', dest='find_copies',
191 help='Disallows git from looking for copies.')
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000192
193 old_parser_args = parser.parse_args
194 def Parse(args):
195 options, args = old_parser_args(args)
196
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000197 if options.similarity is None:
iannucci@chromium.org79540052012-10-19 23:15:26 +0000198 options.similarity = git_get_branch_default('git-cl-similarity', 50)
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000199 else:
iannucci@chromium.org79540052012-10-19 23:15:26 +0000200 print('Note: Saving similarity of %d%% in git config.'
201 % options.similarity)
202 git_set_branch_value('git-cl-similarity', options.similarity)
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000203
iannucci@chromium.org79540052012-10-19 23:15:26 +0000204 options.similarity = max(0, min(options.similarity, 100))
205
206 if options.find_copies is None:
207 options.find_copies = bool(
208 git_get_branch_default('git-find-copies', True))
209 else:
210 git_set_branch_value('git-find-copies', int(options.find_copies))
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000211
212 print('Using %d%% similarity for rename/copy detection. '
213 'Override with --similarity.' % options.similarity)
214
215 return options, args
216 parser.parse_args = Parse
217
218
machenbach@chromium.org45453142015-09-15 08:45:22 +0000219def _get_properties_from_options(options):
220 properties = dict(x.split('=', 1) for x in options.properties)
221 for key, val in properties.iteritems():
222 try:
223 properties[key] = json.loads(val)
224 except ValueError:
225 pass # If a value couldn't be evaluated, treat it as a string.
226 return properties
227
228
sheyang@google.com6ebaf782015-05-12 19:17:54 +0000229def _prefix_master(master):
230 """Convert user-specified master name to full master name.
231
232 Buildbucket uses full master name(master.tryserver.chromium.linux) as bucket
233 name, while the developers always use shortened master name
234 (tryserver.chromium.linux) by stripping off the prefix 'master.'. This
235 function does the conversion for buildbucket migration.
236 """
237 prefix = 'master.'
238 if master.startswith(prefix):
239 return master
240 return '%s%s' % (prefix, master)
241
242
hinoka@chromium.orgfeb9e2a2015-09-25 19:11:09 +0000243def trigger_luci_job(changelist, masters, options):
244 """Send a job to run on LUCI."""
245 issue_props = changelist.GetIssueProperties()
246 issue = changelist.GetIssue()
247 patchset = changelist.GetMostRecentPatchset()
248 for builders_and_tests in sorted(masters.itervalues()):
tandrii@chromium.org3764fa22015-10-21 16:40:40 +0000249 # TODO(hinoka et al): add support for other properties.
250 # Currently, this completely ignores testfilter and other properties.
251 for builder in sorted(builders_and_tests):
hinoka@chromium.orgfeb9e2a2015-09-25 19:11:09 +0000252 luci_trigger.trigger(
253 builder, 'HEAD', issue, patchset, issue_props['project'])
254
255
machenbach@chromium.org45453142015-09-15 08:45:22 +0000256def trigger_try_jobs(auth_config, changelist, options, masters, category):
sheyang@google.com6ebaf782015-05-12 19:17:54 +0000257 rietveld_url = settings.GetDefaultServerUrl()
258 rietveld_host = urlparse.urlparse(rietveld_url).hostname
259 authenticator = auth.get_authenticator_for_host(rietveld_host, auth_config)
260 http = authenticator.authorize(httplib2.Http())
261 http.force_exception_to_status_code = True
262 issue_props = changelist.GetIssueProperties()
263 issue = changelist.GetIssue()
264 patchset = changelist.GetMostRecentPatchset()
machenbach@chromium.org45453142015-09-15 08:45:22 +0000265 properties = _get_properties_from_options(options)
sheyang@google.com6ebaf782015-05-12 19:17:54 +0000266
267 buildbucket_put_url = (
268 'https://{hostname}/_ah/api/buildbucket/v1/builds/batch'.format(
sheyang@chromium.orgdb375572015-08-17 19:22:23 +0000269 hostname=options.buildbucket_host))
sheyang@google.com6ebaf782015-05-12 19:17:54 +0000270 buildset = 'patch/rietveld/{hostname}/{issue}/{patch}'.format(
271 hostname=rietveld_host,
272 issue=issue,
273 patch=patchset)
274
275 batch_req_body = {'builds': []}
276 print_text = []
277 print_text.append('Tried jobs on:')
278 for master, builders_and_tests in sorted(masters.iteritems()):
279 print_text.append('Master: %s' % master)
280 bucket = _prefix_master(master)
281 for builder, tests in sorted(builders_and_tests.iteritems()):
282 print_text.append(' %s: %s' % (builder, tests))
283 parameters = {
284 'builder_name': builder,
nodir@chromium.orgd2217312015-09-21 15:51:21 +0000285 'changes': [{
286 'author': {'email': issue_props['owner_email']},
287 'revision': options.revision,
288 }],
sheyang@google.com6ebaf782015-05-12 19:17:54 +0000289 'properties': {
290 'category': category,
291 'issue': issue,
292 'master': master,
293 'patch_project': issue_props['project'],
294 'patch_storage': 'rietveld',
295 'patchset': patchset,
296 'reason': options.name,
sheyang@google.com6ebaf782015-05-12 19:17:54 +0000297 'rietveld': rietveld_url,
sheyang@google.com6ebaf782015-05-12 19:17:54 +0000298 },
299 }
tandrii@chromium.org3764fa22015-10-21 16:40:40 +0000300 if tests:
301 parameters['properties']['testfilter'] = tests
machenbach@chromium.org45453142015-09-15 08:45:22 +0000302 if properties:
303 parameters['properties'].update(properties)
sheyang@google.com6ebaf782015-05-12 19:17:54 +0000304 if options.clobber:
305 parameters['properties']['clobber'] = True
306 batch_req_body['builds'].append(
307 {
308 'bucket': bucket,
309 'parameters_json': json.dumps(parameters),
310 'tags': ['builder:%s' % builder,
311 'buildset:%s' % buildset,
312 'master:%s' % master,
313 'user_agent:git_cl_try']
314 }
315 )
316
317 for try_count in xrange(3):
318 response, content = http.request(
319 buildbucket_put_url,
320 'PUT',
321 body=json.dumps(batch_req_body),
322 headers={'Content-Type': 'application/json'},
323 )
324 content_json = None
325 try:
326 content_json = json.loads(content)
327 except ValueError:
328 pass
329
330 # Buildbucket could return an error even if status==200.
331 if content_json and content_json.get('error'):
332 msg = 'Error in response. Code: %d. Reason: %s. Message: %s.' % (
333 content_json['error'].get('code', ''),
334 content_json['error'].get('reason', ''),
335 content_json['error'].get('message', ''))
336 raise BuildbucketResponseException(msg)
337
338 if response.status == 200:
339 if not content_json:
340 raise BuildbucketResponseException(
341 'Buildbucket returns invalid json content: %s.\n'
342 'Please file bugs at crbug.com, label "Infra-BuildBucket".' %
343 content)
344 break
345 if response.status < 500 or try_count >= 2:
346 raise httplib2.HttpLib2Error(content)
347
348 # status >= 500 means transient failures.
349 logging.debug('Transient errors when triggering tryjobs. Will retry.')
350 time.sleep(0.5 + 1.5*try_count)
351
352 print '\n'.join(print_text)
kjellander@chromium.org44424542015-06-02 18:35:29 +0000353
sheyang@google.com6ebaf782015-05-12 19:17:54 +0000354
bauerb@chromium.org866276c2011-03-18 20:09:31 +0000355def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
356 """Return the corresponding git ref if |base_url| together with |glob_spec|
357 matches the full |url|.
358
359 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below).
360 """
361 fetch_suburl, as_ref = glob_spec.split(':')
362 if allow_wildcards:
363 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl)
364 if glob_match:
365 # Parse specs like "branches/*/src:refs/remotes/svn/*" or
366 # "branches/{472,597,648}/src:refs/remotes/svn/*".
367 branch_re = re.escape(base_url)
368 if glob_match.group(1):
369 branch_re += '/' + re.escape(glob_match.group(1))
370 wildcard = glob_match.group(2)
371 if wildcard == '*':
372 branch_re += '([^/]*)'
373 else:
374 # Escape and replace surrounding braces with parentheses and commas
375 # with pipe symbols.
376 wildcard = re.escape(wildcard)
377 wildcard = re.sub('^\\\\{', '(', wildcard)
378 wildcard = re.sub('\\\\,', '|', wildcard)
379 wildcard = re.sub('\\\\}$', ')', wildcard)
380 branch_re += wildcard
381 if glob_match.group(3):
382 branch_re += re.escape(glob_match.group(3))
383 match = re.match(branch_re, url)
384 if match:
385 return re.sub('\*$', match.group(1), as_ref)
386
387 # Parse specs like "trunk/src:refs/remotes/origin/trunk".
388 if fetch_suburl:
389 full_url = base_url + '/' + fetch_suburl
390 else:
391 full_url = base_url
392 if full_url == url:
393 return as_ref
394 return None
395
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000396
iannucci@chromium.org79540052012-10-19 23:15:26 +0000397def print_stats(similarity, find_copies, args):
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000398 """Prints statistics about the change to the user."""
399 # --no-ext-diff is broken in some versions of Git, so try to work around
400 # this by overriding the environment (but there is still a problem if the
401 # git config key "diff.external" is used).
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000402 env = GetNoGitPagerEnv()
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000403 if 'GIT_EXTERNAL_DIFF' in env:
404 del env['GIT_EXTERNAL_DIFF']
iannucci@chromium.org79540052012-10-19 23:15:26 +0000405
406 if find_copies:
407 similarity_options = ['--find-copies-harder', '-l100000',
408 '-C%s' % similarity]
409 else:
410 similarity_options = ['-M%s' % similarity]
411
szager@chromium.orgd057f9a2014-05-29 21:09:36 +0000412 try:
413 stdout = sys.stdout.fileno()
414 except AttributeError:
415 stdout = None
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000416 return subprocess2.call(
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000417 ['git',
bratell@opera.comf267b0e2013-05-02 09:11:43 +0000418 'diff', '--no-ext-diff', '--stat'] + similarity_options + args,
szager@chromium.orgd057f9a2014-05-29 21:09:36 +0000419 stdout=stdout, env=env)
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000420
421
sheyang@google.com6ebaf782015-05-12 19:17:54 +0000422class BuildbucketResponseException(Exception):
423 pass
424
425
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000426class Settings(object):
427 def __init__(self):
428 self.default_server = None
429 self.cc = None
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000430 self.root = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000431 self.is_git_svn = None
432 self.svn_branch = None
433 self.tree_status_url = None
434 self.viewvc_url = None
435 self.updated = False
ukai@chromium.orge8077812012-02-03 03:41:46 +0000436 self.is_gerrit = None
bauerb@chromium.org54b400c2016-01-14 10:08:25 +0000437 self.squash_gerrit_uploads = None
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000438 self.git_editor = None
sheyang@chromium.org152cf832014-06-11 21:37:49 +0000439 self.project = None
kjellander@chromium.org6abc6522014-12-02 07:34:49 +0000440 self.force_https_commit_url = None
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000441 self.pending_ref_prefix = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000442
443 def LazyUpdateIfNeeded(self):
444 """Updates the settings from a codereview.settings file, if available."""
445 if not self.updated:
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000446 # The only value that actually changes the behavior is
447 # autoupdate = "false". Everything else means "true".
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000448 autoupdate = RunGit(['config', 'rietveld.autoupdate'],
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000449 error_ok=True
450 ).strip().lower()
451
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000452 cr_settings_file = FindCodereviewSettingsFile()
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000453 if autoupdate != 'false' and cr_settings_file:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000454 LoadCodereviewSettingsFromFile(cr_settings_file)
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000455 # set updated to True to avoid infinite calling loop
456 # through DownloadHooks
ukai@chromium.org78c4b982012-02-14 02:20:26 +0000457 self.updated = True
458 DownloadHooks(False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000459 self.updated = True
460
461 def GetDefaultServerUrl(self, error_ok=False):
462 if not self.default_server:
463 self.LazyUpdateIfNeeded()
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000464 self.default_server = gclient_utils.UpgradeToHttps(
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000465 self._GetRietveldConfig('server', error_ok=True))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000466 if error_ok:
467 return self.default_server
468 if not self.default_server:
469 error_message = ('Could not find settings file. You must configure '
470 'your review setup by running "git cl config".')
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000471 self.default_server = gclient_utils.UpgradeToHttps(
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000472 self._GetRietveldConfig('server', error_message=error_message))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000473 return self.default_server
474
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000475 @staticmethod
476 def GetRelativeRoot():
477 return RunGit(['rev-parse', '--show-cdup']).strip()
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000478
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000479 def GetRoot(self):
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000480 if self.root is None:
481 self.root = os.path.abspath(self.GetRelativeRoot())
482 return self.root
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000483
484 def GetIsGitSvn(self):
485 """Return true if this repo looks like it's using git-svn."""
486 if self.is_git_svn is None:
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000487 if self.GetPendingRefPrefix():
488 # If PENDING_REF_PREFIX is set then it's a pure git repo no matter what.
489 self.is_git_svn = False
490 else:
491 # If you have any "svn-remote.*" config keys, we think you're using svn.
492 self.is_git_svn = RunGitWithCode(
493 ['config', '--local', '--get-regexp', r'^svn-remote\.'])[0] == 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000494 return self.is_git_svn
495
496 def GetSVNBranch(self):
497 if self.svn_branch is None:
498 if not self.GetIsGitSvn():
499 DieWithError('Repo doesn\'t appear to be a git-svn repo.')
500
501 # Try to figure out which remote branch we're based on.
502 # Strategy:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000503 # 1) iterate through our branch history and find the svn URL.
504 # 2) find the svn-remote that fetches from the URL.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000505
506 # regexp matching the git-svn line that contains the URL.
507 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
508
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000509 # We don't want to go through all of history, so read a line from the
510 # pipe at a time.
511 # The -100 is an arbitrary limit so we don't search forever.
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000512 cmd = ['git', 'log', '-100', '--pretty=medium']
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000513 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE,
514 env=GetNoGitPagerEnv())
maruel@chromium.org740f9d72011-06-10 18:33:10 +0000515 url = None
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000516 for line in proc.stdout:
517 match = git_svn_re.match(line)
518 if match:
519 url = match.group(1)
520 proc.stdout.close() # Cut pipe.
521 break
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000522
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000523 if url:
524 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$')
525 remotes = RunGit(['config', '--get-regexp',
526 r'^svn-remote\..*\.url']).splitlines()
527 for remote in remotes:
528 match = svn_remote_re.match(remote)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000529 if match:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000530 remote = match.group(1)
531 base_url = match.group(2)
szager@chromium.org4ac25532013-12-16 22:07:02 +0000532 rewrite_root = RunGit(
533 ['config', 'svn-remote.%s.rewriteRoot' % remote],
534 error_ok=True).strip()
535 if rewrite_root:
536 base_url = rewrite_root
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000537 fetch_spec = RunGit(
bauerb@chromium.org866276c2011-03-18 20:09:31 +0000538 ['config', 'svn-remote.%s.fetch' % remote],
539 error_ok=True).strip()
540 if fetch_spec:
541 self.svn_branch = MatchSvnGlob(url, base_url, fetch_spec, False)
542 if self.svn_branch:
543 break
544 branch_spec = RunGit(
545 ['config', 'svn-remote.%s.branches' % remote],
546 error_ok=True).strip()
547 if branch_spec:
548 self.svn_branch = MatchSvnGlob(url, base_url, branch_spec, True)
549 if self.svn_branch:
550 break
551 tag_spec = RunGit(
552 ['config', 'svn-remote.%s.tags' % remote],
553 error_ok=True).strip()
554 if tag_spec:
555 self.svn_branch = MatchSvnGlob(url, base_url, tag_spec, True)
556 if self.svn_branch:
557 break
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000558
559 if not self.svn_branch:
560 DieWithError('Can\'t guess svn branch -- try specifying it on the '
561 'command line')
562
563 return self.svn_branch
564
565 def GetTreeStatusUrl(self, error_ok=False):
566 if not self.tree_status_url:
567 error_message = ('You must configure your tree status URL by running '
568 '"git cl config".')
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000569 self.tree_status_url = self._GetRietveldConfig(
570 'tree-status-url', error_ok=error_ok, error_message=error_message)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000571 return self.tree_status_url
572
573 def GetViewVCUrl(self):
574 if not self.viewvc_url:
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000575 self.viewvc_url = self._GetRietveldConfig('viewvc-url', error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000576 return self.viewvc_url
577
rmistry@google.com90752582014-01-14 21:04:50 +0000578 def GetBugPrefix(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000579 return self._GetRietveldConfig('bug-prefix', error_ok=True)
rmistry@google.com90752582014-01-14 21:04:50 +0000580
rmistry@google.com78948ed2015-07-08 23:09:57 +0000581 def GetIsSkipDependencyUpload(self, branch_name):
582 """Returns true if specified branch should skip dep uploads."""
583 return self._GetBranchConfig(branch_name, 'skip-deps-uploads',
584 error_ok=True)
585
rmistry@google.com5626a922015-02-26 14:03:30 +0000586 def GetRunPostUploadHook(self):
587 run_post_upload_hook = self._GetRietveldConfig(
588 'run-post-upload-hook', error_ok=True)
589 return run_post_upload_hook == "True"
590
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000591 def GetDefaultCCList(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000592 return self._GetRietveldConfig('cc', error_ok=True)
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000593
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +0000594 def GetDefaultPrivateFlag(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000595 return self._GetRietveldConfig('private', error_ok=True)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +0000596
ukai@chromium.orge8077812012-02-03 03:41:46 +0000597 def GetIsGerrit(self):
598 """Return true if this repo is assosiated with gerrit code review system."""
599 if self.is_gerrit is None:
600 self.is_gerrit = self._GetConfig('gerrit.host', error_ok=True)
601 return self.is_gerrit
602
bauerb@chromium.org54b400c2016-01-14 10:08:25 +0000603 def GetSquashGerritUploads(self):
604 """Return true if uploads to Gerrit should be squashed by default."""
605 if self.squash_gerrit_uploads is None:
606 self.squash_gerrit_uploads = (
607 RunGit(['config', '--bool', 'gerrit.squash-uploads'],
608 error_ok=True).strip() == 'true')
609 return self.squash_gerrit_uploads
610
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000611 def GetGitEditor(self):
612 """Return the editor specified in the git config, or None if none is."""
613 if self.git_editor is None:
614 self.git_editor = self._GetConfig('core.editor', error_ok=True)
615 return self.git_editor or None
616
thestig@chromium.org44202a22014-03-11 19:22:18 +0000617 def GetLintRegex(self):
618 return (self._GetRietveldConfig('cpplint-regex', error_ok=True) or
619 DEFAULT_LINT_REGEX)
620
621 def GetLintIgnoreRegex(self):
622 return (self._GetRietveldConfig('cpplint-ignore-regex', error_ok=True) or
623 DEFAULT_LINT_IGNORE_REGEX)
624
sheyang@chromium.org152cf832014-06-11 21:37:49 +0000625 def GetProject(self):
626 if not self.project:
627 self.project = self._GetRietveldConfig('project', error_ok=True)
628 return self.project
629
kjellander@chromium.org6abc6522014-12-02 07:34:49 +0000630 def GetForceHttpsCommitUrl(self):
631 if not self.force_https_commit_url:
632 self.force_https_commit_url = self._GetRietveldConfig(
633 'force-https-commit-url', error_ok=True)
634 return self.force_https_commit_url
635
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000636 def GetPendingRefPrefix(self):
637 if not self.pending_ref_prefix:
638 self.pending_ref_prefix = self._GetRietveldConfig(
639 'pending-ref-prefix', error_ok=True)
640 return self.pending_ref_prefix
641
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000642 def _GetRietveldConfig(self, param, **kwargs):
643 return self._GetConfig('rietveld.' + param, **kwargs)
644
rmistry@google.com78948ed2015-07-08 23:09:57 +0000645 def _GetBranchConfig(self, branch_name, param, **kwargs):
646 return self._GetConfig('branch.' + branch_name + '.' + param, **kwargs)
647
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000648 def _GetConfig(self, param, **kwargs):
649 self.LazyUpdateIfNeeded()
650 return RunGit(['config', param], **kwargs).strip()
651
652
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000653def ShortBranchName(branch):
654 """Convert a name like 'refs/heads/foo' to just 'foo'."""
655 return branch.replace('refs/heads/', '')
656
657
658class Changelist(object):
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +0000659 def __init__(self, branchref=None, issue=None, auth_config=None):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000660 # Poke settings so we get the "configure your server" message if necessary.
maruel@chromium.org379d07a2011-11-30 14:58:10 +0000661 global settings
662 if not settings:
663 # Happens when git_cl.py is used as a utility library.
664 settings = Settings()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000665 settings.GetDefaultServerUrl()
666 self.branchref = branchref
667 if self.branchref:
668 self.branch = ShortBranchName(self.branchref)
669 else:
670 self.branch = None
671 self.rietveld_server = None
672 self.upstream_branch = None
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000673 self.lookedup_issue = False
674 self.issue = issue or None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000675 self.has_description = False
676 self.description = None
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000677 self.lookedup_patchset = False
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000678 self.patchset = None
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000679 self.cc = None
680 self.watchers = ()
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +0000681 self._auth_config = auth_config
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000682 self._props = None
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +0000683 self._remote = None
684 self._rpc_server = None
685
686 @property
687 def auth_config(self):
688 return self._auth_config
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000689
690 def GetCCList(self):
691 """Return the users cc'd on this CL.
692
693 Return is a string suitable for passing to gcl with the --cc flag.
694 """
695 if self.cc is None:
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +0000696 base_cc = settings.GetDefaultCCList()
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000697 more_cc = ','.join(self.watchers)
698 self.cc = ','.join(filter(None, (base_cc, more_cc))) or ''
699 return self.cc
700
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +0000701 def GetCCListWithoutDefault(self):
702 """Return the users cc'd on this CL excluding default ones."""
703 if self.cc is None:
704 self.cc = ','.join(self.watchers)
705 return self.cc
706
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000707 def SetWatchers(self, watchers):
708 """Set the list of email addresses that should be cc'd based on the changed
709 files in this CL.
710 """
711 self.watchers = watchers
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000712
713 def GetBranch(self):
714 """Returns the short branch name, e.g. 'master'."""
715 if not self.branch:
szager@chromium.orgd62c61f2014-10-20 22:33:21 +0000716 branchref = RunGit(['symbolic-ref', 'HEAD'],
717 stderr=subprocess2.VOID, error_ok=True).strip()
718 if not branchref:
719 return None
720 self.branchref = branchref
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000721 self.branch = ShortBranchName(self.branchref)
722 return self.branch
723
724 def GetBranchRef(self):
725 """Returns the full branch name, e.g. 'refs/heads/master'."""
726 self.GetBranch() # Poke the lazy loader.
727 return self.branchref
728
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000729 @staticmethod
730 def FetchUpstreamTuple(branch):
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +0000731 """Returns a tuple containing remote and remote ref,
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000732 e.g. 'origin', 'refs/heads/master'
733 """
734 remote = '.'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000735 upstream_branch = RunGit(['config', 'branch.%s.merge' % branch],
736 error_ok=True).strip()
737 if upstream_branch:
738 remote = RunGit(['config', 'branch.%s.remote' % branch]).strip()
739 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000740 upstream_branch = RunGit(['config', 'rietveld.upstream-branch'],
741 error_ok=True).strip()
742 if upstream_branch:
743 remote = RunGit(['config', 'rietveld.upstream-remote']).strip()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000744 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000745 # Fall back on trying a git-svn upstream branch.
746 if settings.GetIsGitSvn():
747 upstream_branch = settings.GetSVNBranch()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000748 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000749 # Else, try to guess the origin remote.
750 remote_branches = RunGit(['branch', '-r']).split()
751 if 'origin/master' in remote_branches:
752 # Fall back on origin/master if it exits.
753 remote = 'origin'
754 upstream_branch = 'refs/heads/master'
755 elif 'origin/trunk' in remote_branches:
756 # Fall back on origin/trunk if it exists. Generally a shared
757 # git-svn clone
758 remote = 'origin'
759 upstream_branch = 'refs/heads/trunk'
760 else:
761 DieWithError("""Unable to determine default branch to diff against.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000762Either pass complete "git diff"-style arguments, like
763 git cl upload origin/master
764or verify this branch is set up to track another (via the --track argument to
765"git checkout -b ...").""")
766
767 return remote, upstream_branch
768
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000769 def GetCommonAncestorWithUpstream(self):
pgervais@chromium.org8ba38ff2015-06-11 21:41:25 +0000770 upstream_branch = self.GetUpstreamBranch()
771 if not BranchExists(upstream_branch):
772 DieWithError('The upstream for the current branch (%s) does not exist '
773 'anymore.\nPlease fix it and try again.' % self.GetBranch())
iannucci@chromium.org9e849272014-04-04 00:31:55 +0000774 return git_common.get_or_create_merge_base(self.GetBranch(),
pgervais@chromium.org8ba38ff2015-06-11 21:41:25 +0000775 upstream_branch)
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000776
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000777 def GetUpstreamBranch(self):
778 if self.upstream_branch is None:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000779 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000780 if remote is not '.':
mmoss@chromium.orge7585452014-08-24 01:41:11 +0000781 upstream_branch = upstream_branch.replace('refs/heads/',
782 'refs/remotes/%s/' % remote)
783 upstream_branch = upstream_branch.replace('refs/branch-heads/',
784 'refs/remotes/branch-heads/')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000785 self.upstream_branch = upstream_branch
786 return self.upstream_branch
787
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000788 def GetRemoteBranch(self):
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000789 if not self._remote:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000790 remote, branch = None, self.GetBranch()
791 seen_branches = set()
792 while branch not in seen_branches:
793 seen_branches.add(branch)
794 remote, branch = self.FetchUpstreamTuple(branch)
795 branch = ShortBranchName(branch)
796 if remote != '.' or branch.startswith('refs/remotes'):
797 break
798 else:
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000799 remotes = RunGit(['remote'], error_ok=True).split()
800 if len(remotes) == 1:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000801 remote, = remotes
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000802 elif 'origin' in remotes:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000803 remote = 'origin'
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000804 logging.warning('Could not determine which remote this change is '
805 'associated with, so defaulting to "%s". This may '
806 'not be what you want. You may prevent this message '
807 'by running "git svn info" as documented here: %s',
808 self._remote,
809 GIT_INSTRUCTIONS_URL)
810 else:
811 logging.warn('Could not determine which remote this change is '
812 'associated with. You may prevent this message by '
813 'running "git svn info" as documented here: %s',
814 GIT_INSTRUCTIONS_URL)
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000815 branch = 'HEAD'
816 if branch.startswith('refs/remotes'):
817 self._remote = (remote, branch)
mmoss@chromium.orge7585452014-08-24 01:41:11 +0000818 elif branch.startswith('refs/branch-heads/'):
819 self._remote = (remote, branch.replace('refs/', 'refs/remotes/'))
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000820 else:
821 self._remote = (remote, 'refs/remotes/%s/%s' % (remote, branch))
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000822 return self._remote
823
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000824 def GitSanityChecks(self, upstream_git_obj):
825 """Checks git repo status and ensures diff is from local commits."""
826
sbc@chromium.org79706062015-01-14 21:18:12 +0000827 if upstream_git_obj is None:
828 if self.GetBranch() is None:
829 print >> sys.stderr, (
dpranke@chromium.orgee87f582015-07-31 18:46:25 +0000830 'ERROR: unable to determine current branch (detached HEAD?)')
sbc@chromium.org79706062015-01-14 21:18:12 +0000831 else:
832 print >> sys.stderr, (
833 'ERROR: no upstream branch')
834 return False
835
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000836 # Verify the commit we're diffing against is in our current branch.
837 upstream_sha = RunGit(['rev-parse', '--verify', upstream_git_obj]).strip()
838 common_ancestor = RunGit(['merge-base', upstream_sha, 'HEAD']).strip()
839 if upstream_sha != common_ancestor:
840 print >> sys.stderr, (
841 'ERROR: %s is not in the current branch. You may need to rebase '
842 'your tracking branch' % upstream_sha)
843 return False
844
845 # List the commits inside the diff, and verify they are all local.
846 commits_in_diff = RunGit(
847 ['rev-list', '^%s' % upstream_sha, 'HEAD']).splitlines()
848 code, remote_branch = RunGitWithCode(['config', 'gitcl.remotebranch'])
849 remote_branch = remote_branch.strip()
850 if code != 0:
851 _, remote_branch = self.GetRemoteBranch()
852
853 commits_in_remote = RunGit(
854 ['rev-list', '^%s' % upstream_sha, remote_branch]).splitlines()
855
856 common_commits = set(commits_in_diff) & set(commits_in_remote)
857 if common_commits:
858 print >> sys.stderr, (
859 'ERROR: Your diff contains %d commits already in %s.\n'
860 'Run "git log --oneline %s..HEAD" to get a list of commits in '
861 'the diff. If you are using a custom git flow, you can override'
862 ' the reference used for this check with "git config '
863 'gitcl.remotebranch <git-ref>".' % (
864 len(common_commits), remote_branch, upstream_git_obj))
865 return False
866 return True
867
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +0000868 def GetGitBaseUrlFromConfig(self):
sheyang@chromium.orga656e702014-05-15 20:43:05 +0000869 """Return the configured base URL from branch.<branchname>.baseurl.
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +0000870
871 Returns None if it is not set.
872 """
sheyang@chromium.orga656e702014-05-15 20:43:05 +0000873 return RunGit(['config', 'branch.%s.base-url' % self.GetBranch()],
874 error_ok=True).strip()
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000875
kjellander@chromium.org6abc6522014-12-02 07:34:49 +0000876 def GetGitSvnRemoteUrl(self):
877 """Return the configured git-svn remote URL parsed from git svn info.
878
879 Returns None if it is not set.
880 """
881 # URL is dependent on the current directory.
882 data = RunGit(['svn', 'info'], cwd=settings.GetRoot())
883 if data:
884 keys = dict(line.split(': ', 1) for line in data.splitlines()
885 if ': ' in line)
886 return keys.get('URL', None)
887 return None
888
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000889 def GetRemoteUrl(self):
890 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'.
891
892 Returns None if there is no remote.
893 """
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000894 remote, _ = self.GetRemoteBranch()
dyen@chromium.org2a13d4f2014-06-13 00:06:37 +0000895 url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip()
896
897 # If URL is pointing to a local directory, it is probably a git cache.
898 if os.path.isdir(url):
899 url = RunGit(['config', 'remote.%s.url' % remote],
900 error_ok=True,
901 cwd=url).strip()
902 return url
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000903
904 def GetIssue(self):
maruel@chromium.org52424302012-08-29 15:14:30 +0000905 """Returns the issue number as a int or None if not set."""
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000906 if self.issue is None and not self.lookedup_issue:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000907 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip()
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000908 self.issue = int(issue) or None if issue else None
909 self.lookedup_issue = True
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000910 return self.issue
911
912 def GetRietveldServer(self):
evan@chromium.org0af9b702012-02-11 00:42:16 +0000913 if not self.rietveld_server:
914 # If we're on a branch then get the server potentially associated
915 # with that branch.
916 if self.GetIssue():
szager@chromium.orgd62c61f2014-10-20 22:33:21 +0000917 rietveld_server_config = self._RietveldServer()
918 if rietveld_server_config:
919 self.rietveld_server = gclient_utils.UpgradeToHttps(RunGit(
920 ['config', rietveld_server_config], error_ok=True).strip())
evan@chromium.org0af9b702012-02-11 00:42:16 +0000921 if not self.rietveld_server:
922 self.rietveld_server = settings.GetDefaultServerUrl()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000923 return self.rietveld_server
924
925 def GetIssueURL(self):
926 """Get the URL for a particular issue."""
dbeam@chromium.org015fd3d2013-06-18 19:02:50 +0000927 if not self.GetIssue():
928 return None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000929 return '%s/%s' % (self.GetRietveldServer(), self.GetIssue())
930
931 def GetDescription(self, pretty=False):
932 if not self.has_description:
933 if self.GetIssue():
maruel@chromium.org52424302012-08-29 15:14:30 +0000934 issue = self.GetIssue()
miket@chromium.org183df1a2012-01-04 19:44:55 +0000935 try:
936 self.description = self.RpcServer().get_description(issue).strip()
maruel@chromium.org85616e02014-07-28 15:37:55 +0000937 except urllib2.HTTPError as e:
miket@chromium.org183df1a2012-01-04 19:44:55 +0000938 if e.code == 404:
939 DieWithError(
940 ('\nWhile fetching the description for issue %d, received a '
941 '404 (not found)\n'
942 'error. It is likely that you deleted this '
943 'issue on the server. If this is the\n'
944 'case, please run\n\n'
945 ' git cl issue 0\n\n'
946 'to clear the association with the deleted issue. Then run '
947 'this command again.') % issue)
948 else:
949 DieWithError(
yujie.mao@intel.comdaee1d32013-12-18 11:55:03 +0000950 '\nFailed to fetch issue description. HTTP error %d' % e.code)
maruel@chromium.org85616e02014-07-28 15:37:55 +0000951 except urllib2.URLError as e:
952 print >> sys.stderr, (
953 'Warning: Failed to retrieve CL description due to network '
954 'failure.')
955 self.description = ''
956
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000957 self.has_description = True
958 if pretty:
959 wrapper = textwrap.TextWrapper()
960 wrapper.initial_indent = wrapper.subsequent_indent = ' '
961 return wrapper.fill(self.description)
962 return self.description
963
964 def GetPatchset(self):
maruel@chromium.org52424302012-08-29 15:14:30 +0000965 """Returns the patchset number as a int or None if not set."""
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000966 if self.patchset is None and not self.lookedup_patchset:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000967 patchset = RunGit(['config', self._PatchsetSetting()],
968 error_ok=True).strip()
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000969 self.patchset = int(patchset) or None if patchset else None
970 self.lookedup_patchset = True
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000971 return self.patchset
972
973 def SetPatchset(self, patchset):
974 """Set this branch's patchset. If patchset=0, clears the patchset."""
975 if patchset:
976 RunGit(['config', self._PatchsetSetting(), str(patchset)])
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000977 self.patchset = patchset
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000978 else:
979 RunGit(['config', '--unset', self._PatchsetSetting()],
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000980 stderr=subprocess2.PIPE, error_ok=True)
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000981 self.patchset = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000982
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000983 def GetMostRecentPatchset(self):
984 return self.GetIssueProperties()['patchsets'][-1]
binji@chromium.org0281f522012-09-14 13:37:59 +0000985
986 def GetPatchSetDiff(self, issue, patchset):
maruel@chromium.org27bb3872011-05-30 20:33:19 +0000987 return self.RpcServer().get(
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000988 '/download/issue%s_%s.diff' % (issue, patchset))
989
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000990 def GetIssueProperties(self):
991 if self._props is None:
992 issue = self.GetIssue()
993 if not issue:
994 self._props = {}
995 else:
996 self._props = self.RpcServer().get_issue_properties(issue, True)
997 return self._props
998
maruel@chromium.orgcf087782013-07-23 13:08:48 +0000999 def GetApprovingReviewers(self):
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001000 return get_approving_reviewers(self.GetIssueProperties())
maruel@chromium.orge52678e2013-04-26 18:34:44 +00001001
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001002 def AddComment(self, message):
1003 return self.RpcServer().add_comment(self.GetIssue(), message)
1004
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001005 def SetIssue(self, issue):
1006 """Set this branch's issue. If issue=0, clears the issue."""
1007 if issue:
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001008 self.issue = issue
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001009 RunGit(['config', self._IssueSetting(), str(issue)])
1010 if self.rietveld_server:
1011 RunGit(['config', self._RietveldServer(), self.rietveld_server])
1012 else:
teravest@chromium.orgd79d4b82013-10-23 20:09:08 +00001013 current_issue = self.GetIssue()
1014 if current_issue:
1015 RunGit(['config', '--unset', self._IssueSetting()])
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001016 self.issue = None
1017 self.SetPatchset(None)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001018
asvitkine@chromium.org15169952011-09-27 14:30:53 +00001019 def GetChange(self, upstream_branch, author):
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00001020 if not self.GitSanityChecks(upstream_branch):
1021 DieWithError('\nGit sanity check failure')
1022
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001023 root = settings.GetRelativeRoot()
bratell@opera.comf267b0e2013-05-02 09:11:43 +00001024 if not root:
1025 root = '.'
bauerb@chromium.org512f1ef2011-04-20 15:17:57 +00001026 absroot = os.path.abspath(root)
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +00001027
1028 # We use the sha1 of HEAD as a name of this change.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001029 name = RunGitWithCode(['rev-parse', 'HEAD'])[1].strip()
bauerb@chromium.org512f1ef2011-04-20 15:17:57 +00001030 # Need to pass a relative path for msysgit.
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +00001031 try:
maruel@chromium.org80a9ef12011-12-13 20:44:10 +00001032 files = scm.GIT.CaptureStatus([root], '.', upstream_branch)
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +00001033 except subprocess2.CalledProcessError:
1034 DieWithError(
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +00001035 ('\nFailed to diff against upstream branch %s\n\n'
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +00001036 'This branch probably doesn\'t exist anymore. To reset the\n'
1037 'tracking branch, please run\n'
1038 ' git branch --set-upstream %s trunk\n'
1039 'replacing trunk with origin/master or the relevant branch') %
1040 (upstream_branch, self.GetBranch()))
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +00001041
maruel@chromium.org52424302012-08-29 15:14:30 +00001042 issue = self.GetIssue()
1043 patchset = self.GetPatchset()
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +00001044 if issue:
1045 description = self.GetDescription()
1046 else:
1047 # If the change was never uploaded, use the log messages of all commits
1048 # up to the branch point, as git cl upload will prefill the description
1049 # with these log messages.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001050 args = ['log', '--pretty=format:%s%n%n%b', '%s...' % (upstream_branch)]
1051 description = RunGitWithCode(args)[1].strip()
maruel@chromium.org03b3bdc2011-06-14 13:04:12 +00001052
1053 if not author:
maruel@chromium.org13f623c2011-07-22 16:02:23 +00001054 author = RunGit(['config', 'user.email']).strip() or None
asvitkine@chromium.org15169952011-09-27 14:30:53 +00001055 return presubmit_support.GitChange(
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +00001056 name,
1057 description,
1058 absroot,
1059 files,
1060 issue,
1061 patchset,
agable@chromium.orgea84ef12014-04-30 19:55:12 +00001062 author,
1063 upstream=upstream_branch)
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +00001064
jsbell@chromium.orgb99fbd92014-09-11 17:29:28 +00001065 def GetStatus(self):
1066 """Apply a rough heuristic to give a simple summary of an issue's review
1067 or CQ status, assuming adherence to a common workflow.
1068
1069 Returns None if no issue for this branch, or one of the following keywords:
1070 * 'error' - error from review tool (including deleted issues)
1071 * 'unsent' - not sent for review
1072 * 'waiting' - waiting for review
1073 * 'reply' - waiting for owner to reply to review
1074 * 'lgtm' - LGTM from at least one approved reviewer
1075 * 'commit' - in the commit queue
1076 * 'closed' - closed
1077 """
1078 if not self.GetIssue():
1079 return None
1080
1081 try:
1082 props = self.GetIssueProperties()
1083 except urllib2.HTTPError:
1084 return 'error'
1085
1086 if props.get('closed'):
1087 # Issue is closed.
1088 return 'closed'
1089 if props.get('commit'):
1090 # Issue is in the commit queue.
1091 return 'commit'
1092
1093 try:
1094 reviewers = self.GetApprovingReviewers()
1095 except urllib2.HTTPError:
1096 return 'error'
1097
1098 if reviewers:
1099 # Was LGTM'ed.
1100 return 'lgtm'
1101
1102 messages = props.get('messages') or []
1103
1104 if not messages:
1105 # No message was sent.
1106 return 'unsent'
1107 if messages[-1]['sender'] != props.get('owner_email'):
1108 # Non-LGTM reply from non-owner
1109 return 'reply'
1110 return 'waiting'
1111
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00001112 def RunHook(self, committing, may_prompt, verbose, change):
asvitkine@chromium.org15169952011-09-27 14:30:53 +00001113 """Calls sys.exit() if the hook fails; returns a HookResults otherwise."""
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +00001114
1115 try:
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00001116 return presubmit_support.DoPresubmitChecks(change, committing,
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +00001117 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin,
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00001118 default_presubmit=None, may_prompt=may_prompt,
maruel@chromium.org239f4112011-06-03 20:08:23 +00001119 rietveld_obj=self.RpcServer())
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +00001120 except presubmit_support.PresubmitFailure, e:
1121 DieWithError(
1122 ('%s\nMaybe your depot_tools is out of date?\n'
1123 'If all fails, contact maruel@') % e)
1124
maruel@chromium.orgb021b322013-04-08 17:57:29 +00001125 def UpdateDescription(self, description):
1126 self.description = description
1127 return self.RpcServer().update_description(
1128 self.GetIssue(), self.description)
1129
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001130 def CloseIssue(self):
maruel@chromium.org607bb1b2011-06-01 23:43:11 +00001131 """Updates the description and closes the issue."""
maruel@chromium.orgb021b322013-04-08 17:57:29 +00001132 return self.RpcServer().close_issue(self.GetIssue())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001133
maruel@chromium.org27bb3872011-05-30 20:33:19 +00001134 def SetFlag(self, flag, value):
1135 """Patchset must match."""
1136 if not self.GetPatchset():
1137 DieWithError('The patchset needs to match. Send another patchset.')
1138 try:
1139 return self.RpcServer().set_flag(
maruel@chromium.org52424302012-08-29 15:14:30 +00001140 self.GetIssue(), self.GetPatchset(), flag, value)
maruel@chromium.org27bb3872011-05-30 20:33:19 +00001141 except urllib2.HTTPError, e:
1142 if e.code == 404:
1143 DieWithError('The issue %s doesn\'t exist.' % self.GetIssue())
1144 if e.code == 403:
1145 DieWithError(
1146 ('Access denied to issue %s. Maybe the patchset %s doesn\'t '
1147 'match?') % (self.GetIssue(), self.GetPatchset()))
1148 raise
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001149
maruel@chromium.orgcab38e92011-04-09 00:30:51 +00001150 def RpcServer(self):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001151 """Returns an upload.RpcServer() to access this review's rietveld instance.
1152 """
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00001153 if not self._rpc_server:
maruel@chromium.org4bac4b52012-11-27 20:33:52 +00001154 self._rpc_server = rietveld.CachingRietveld(
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001155 self.GetRietveldServer(),
1156 self._auth_config or auth.make_auth_config())
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00001157 return self._rpc_server
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001158
1159 def _IssueSetting(self):
1160 """Return the git setting that stores this change's issue."""
1161 return 'branch.%s.rietveldissue' % self.GetBranch()
1162
1163 def _PatchsetSetting(self):
1164 """Return the git setting that stores this change's most recent patchset."""
1165 return 'branch.%s.rietveldpatchset' % self.GetBranch()
1166
1167 def _RietveldServer(self):
1168 """Returns the git setting that stores this change's rietveld server."""
szager@chromium.orgd62c61f2014-10-20 22:33:21 +00001169 branch = self.GetBranch()
1170 if branch:
1171 return 'branch.%s.rietveldserver' % branch
1172 return None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001173
1174
1175def GetCodereviewSettingsInteractively():
1176 """Prompt the user for settings."""
ukai@chromium.orge8077812012-02-03 03:41:46 +00001177 # TODO(ukai): ask code review system is rietveld or gerrit?
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001178 server = settings.GetDefaultServerUrl(error_ok=True)
1179 prompt = 'Rietveld server (host[:port])'
1180 prompt += ' [%s]' % (server or DEFAULT_SERVER)
maruel@chromium.org90541732011-04-01 17:54:18 +00001181 newserver = ask_for_data(prompt + ':')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001182 if not server and not newserver:
1183 newserver = DEFAULT_SERVER
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001184 if newserver:
1185 newserver = gclient_utils.UpgradeToHttps(newserver)
1186 if newserver != server:
1187 RunGit(['config', 'rietveld.server', newserver])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001188
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001189 def SetProperty(initial, caption, name, is_url):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001190 prompt = caption
1191 if initial:
1192 prompt += ' ("x" to clear) [%s]' % initial
maruel@chromium.org90541732011-04-01 17:54:18 +00001193 new_val = ask_for_data(prompt + ':')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001194 if new_val == 'x':
1195 RunGit(['config', '--unset-all', 'rietveld.' + name], error_ok=True)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001196 elif new_val:
1197 if is_url:
1198 new_val = gclient_utils.UpgradeToHttps(new_val)
1199 if new_val != initial:
1200 RunGit(['config', 'rietveld.' + name, new_val])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001201
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001202 SetProperty(settings.GetDefaultCCList(), 'CC list', 'cc', False)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001203 SetProperty(settings.GetDefaultPrivateFlag(),
1204 'Private flag (rietveld only)', 'private', False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001205 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL',
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001206 'tree-status-url', False)
1207 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url', True)
rmistry@google.com90752582014-01-14 21:04:50 +00001208 SetProperty(settings.GetBugPrefix(), 'Bug Prefix', 'bug-prefix', False)
rmistry@google.com5626a922015-02-26 14:03:30 +00001209 SetProperty(settings.GetRunPostUploadHook(), 'Run Post Upload Hook',
1210 'run-post-upload-hook', False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001211
1212 # TODO: configure a default branch to diff against, rather than this
1213 # svn-based hackery.
1214
1215
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001216class ChangeDescription(object):
1217 """Contains a parsed form of the change description."""
maruel@chromium.orgc6f60e82013-04-19 17:01:57 +00001218 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$'
agable@chromium.org42c20792013-09-12 17:34:49 +00001219 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$'
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001220
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001221 def __init__(self, description):
agable@chromium.org42c20792013-09-12 17:34:49 +00001222 self._description_lines = (description or '').strip().splitlines()
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001223
agable@chromium.org42c20792013-09-12 17:34:49 +00001224 @property # www.logilab.org/ticket/89786
1225 def description(self): # pylint: disable=E0202
1226 return '\n'.join(self._description_lines)
1227
1228 def set_description(self, desc):
1229 if isinstance(desc, basestring):
1230 lines = desc.splitlines()
1231 else:
1232 lines = [line.rstrip() for line in desc]
1233 while lines and not lines[0]:
1234 lines.pop(0)
1235 while lines and not lines[-1]:
1236 lines.pop(-1)
1237 self._description_lines = lines
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001238
piman@chromium.org336f9122014-09-04 02:16:55 +00001239 def update_reviewers(self, reviewers, add_owners_tbr=False, change=None):
agable@chromium.org42c20792013-09-12 17:34:49 +00001240 """Rewrites the R=/TBR= line(s) as a single line each."""
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001241 assert isinstance(reviewers, list), reviewers
piman@chromium.org336f9122014-09-04 02:16:55 +00001242 if not reviewers and not add_owners_tbr:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001243 return
agable@chromium.org42c20792013-09-12 17:34:49 +00001244 reviewers = reviewers[:]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001245
agable@chromium.org42c20792013-09-12 17:34:49 +00001246 # Get the set of R= and TBR= lines and remove them from the desciption.
1247 regexp = re.compile(self.R_LINE)
1248 matches = [regexp.match(line) for line in self._description_lines]
1249 new_desc = [l for i, l in enumerate(self._description_lines)
1250 if not matches[i]]
1251 self.set_description(new_desc)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001252
agable@chromium.org42c20792013-09-12 17:34:49 +00001253 # Construct new unified R= and TBR= lines.
1254 r_names = []
1255 tbr_names = []
1256 for match in matches:
1257 if not match:
1258 continue
1259 people = cleanup_list([match.group(2).strip()])
1260 if match.group(1) == 'TBR':
1261 tbr_names.extend(people)
1262 else:
1263 r_names.extend(people)
1264 for name in r_names:
1265 if name not in reviewers:
1266 reviewers.append(name)
piman@chromium.org336f9122014-09-04 02:16:55 +00001267 if add_owners_tbr:
1268 owners_db = owners.Database(change.RepositoryRoot(),
1269 fopen=file, os_path=os.path, glob=glob.glob)
1270 all_reviewers = set(tbr_names + reviewers)
1271 missing_files = owners_db.files_not_covered_by(change.LocalPaths(),
1272 all_reviewers)
1273 tbr_names.extend(owners_db.reviewers_for(missing_files,
1274 change.author_email))
agable@chromium.org42c20792013-09-12 17:34:49 +00001275 new_r_line = 'R=' + ', '.join(reviewers) if reviewers else None
1276 new_tbr_line = 'TBR=' + ', '.join(tbr_names) if tbr_names else None
1277
1278 # Put the new lines in the description where the old first R= line was.
1279 line_loc = next((i for i, match in enumerate(matches) if match), -1)
1280 if 0 <= line_loc < len(self._description_lines):
1281 if new_tbr_line:
1282 self._description_lines.insert(line_loc, new_tbr_line)
1283 if new_r_line:
1284 self._description_lines.insert(line_loc, new_r_line)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001285 else:
agable@chromium.org42c20792013-09-12 17:34:49 +00001286 if new_r_line:
1287 self.append_footer(new_r_line)
1288 if new_tbr_line:
1289 self.append_footer(new_tbr_line)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001290
1291 def prompt(self):
1292 """Asks the user to update the description."""
agable@chromium.org42c20792013-09-12 17:34:49 +00001293 self.set_description([
1294 '# Enter a description of the change.',
1295 '# This will be displayed on the codereview site.',
1296 '# The first line will also be used as the subject of the review.',
alancutter@chromium.orgbd1073e2013-06-01 00:34:38 +00001297 '#--------------------This line is 72 characters long'
agable@chromium.org42c20792013-09-12 17:34:49 +00001298 '--------------------',
1299 ] + self._description_lines)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001300
agable@chromium.org42c20792013-09-12 17:34:49 +00001301 regexp = re.compile(self.BUG_LINE)
1302 if not any((regexp.match(line) for line in self._description_lines)):
rmistry@google.com90752582014-01-14 21:04:50 +00001303 self.append_footer('BUG=%s' % settings.GetBugPrefix())
agable@chromium.org42c20792013-09-12 17:34:49 +00001304 content = gclient_utils.RunEditor(self.description, True,
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001305 git_editor=settings.GetGitEditor())
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001306 if not content:
1307 DieWithError('Running editor failed')
agable@chromium.org42c20792013-09-12 17:34:49 +00001308 lines = content.splitlines()
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001309
1310 # Strip off comments.
agable@chromium.org42c20792013-09-12 17:34:49 +00001311 clean_lines = [line.rstrip() for line in lines if not line.startswith('#')]
1312 if not clean_lines:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001313 DieWithError('No CL description, aborting')
agable@chromium.org42c20792013-09-12 17:34:49 +00001314 self.set_description(clean_lines)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001315
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001316 def append_footer(self, line):
agable@chromium.org42c20792013-09-12 17:34:49 +00001317 if self._description_lines:
1318 # Add an empty line if either the last line or the new line isn't a tag.
1319 last_line = self._description_lines[-1]
1320 if (not presubmit_support.Change.TAG_LINE_RE.match(last_line) or
1321 not presubmit_support.Change.TAG_LINE_RE.match(line)):
1322 self._description_lines.append('')
1323 self._description_lines.append(line)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001324
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001325 def get_reviewers(self):
1326 """Retrieves the list of reviewers."""
agable@chromium.org42c20792013-09-12 17:34:49 +00001327 matches = [re.match(self.R_LINE, line) for line in self._description_lines]
1328 reviewers = [match.group(2).strip() for match in matches if match]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001329 return cleanup_list(reviewers)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001330
1331
maruel@chromium.orge52678e2013-04-26 18:34:44 +00001332def get_approving_reviewers(props):
1333 """Retrieves the reviewers that approved a CL from the issue properties with
1334 messages.
1335
1336 Note that the list may contain reviewers that are not committer, thus are not
1337 considered by the CQ.
1338 """
1339 return sorted(
1340 set(
1341 message['sender']
1342 for message in props['messages']
1343 if message['approval'] and message['sender'] in props['reviewers']
1344 )
1345 )
1346
1347
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001348def FindCodereviewSettingsFile(filename='codereview.settings'):
1349 """Finds the given file starting in the cwd and going up.
1350
1351 Only looks up to the top of the repository unless an
1352 'inherit-review-settings-ok' file exists in the root of the repository.
1353 """
1354 inherit_ok_file = 'inherit-review-settings-ok'
1355 cwd = os.getcwd()
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001356 root = settings.GetRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001357 if os.path.isfile(os.path.join(root, inherit_ok_file)):
1358 root = '/'
1359 while True:
1360 if filename in os.listdir(cwd):
1361 if os.path.isfile(os.path.join(cwd, filename)):
1362 return open(os.path.join(cwd, filename))
1363 if cwd == root:
1364 break
1365 cwd = os.path.dirname(cwd)
1366
1367
1368def LoadCodereviewSettingsFromFile(fileobj):
1369 """Parse a codereview.settings file and updates hooks."""
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001370 keyvals = gclient_utils.ParseCodereviewSettingsContent(fileobj.read())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001371
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001372 def SetProperty(name, setting, unset_error_ok=False):
1373 fullname = 'rietveld.' + name
1374 if setting in keyvals:
1375 RunGit(['config', fullname, keyvals[setting]])
1376 else:
1377 RunGit(['config', '--unset-all', fullname], error_ok=unset_error_ok)
1378
1379 SetProperty('server', 'CODE_REVIEW_SERVER')
1380 # Only server setting is required. Other settings can be absent.
1381 # In that case, we ignore errors raised during option deletion attempt.
1382 SetProperty('cc', 'CC_LIST', unset_error_ok=True)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001383 SetProperty('private', 'PRIVATE', unset_error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001384 SetProperty('tree-status-url', 'STATUS', unset_error_ok=True)
1385 SetProperty('viewvc-url', 'VIEW_VC', unset_error_ok=True)
rmistry@google.com90752582014-01-14 21:04:50 +00001386 SetProperty('bug-prefix', 'BUG_PREFIX', unset_error_ok=True)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001387 SetProperty('cpplint-regex', 'LINT_REGEX', unset_error_ok=True)
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00001388 SetProperty('force-https-commit-url', 'FORCE_HTTPS_COMMIT_URL',
1389 unset_error_ok=True)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001390 SetProperty('cpplint-ignore-regex', 'LINT_IGNORE_REGEX', unset_error_ok=True)
sheyang@chromium.org152cf832014-06-11 21:37:49 +00001391 SetProperty('project', 'PROJECT', unset_error_ok=True)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00001392 SetProperty('pending-ref-prefix', 'PENDING_REF_PREFIX', unset_error_ok=True)
rmistry@google.com5626a922015-02-26 14:03:30 +00001393 SetProperty('run-post-upload-hook', 'RUN_POST_UPLOAD_HOOK',
1394 unset_error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001395
ukai@chromium.org7044efc2013-11-28 01:51:21 +00001396 if 'GERRIT_HOST' in keyvals:
ukai@chromium.orge8077812012-02-03 03:41:46 +00001397 RunGit(['config', 'gerrit.host', keyvals['GERRIT_HOST']])
ukai@chromium.orge8077812012-02-03 03:41:46 +00001398
bauerb@chromium.org54b400c2016-01-14 10:08:25 +00001399 if 'GERRIT_SQUASH_UPLOADS' in keyvals:
1400 RunGit(['config', 'gerrit.squash-uploads',
1401 keyvals['GERRIT_SQUASH_UPLOADS']])
1402
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001403 if 'PUSH_URL_CONFIG' in keyvals and 'ORIGIN_URL_CONFIG' in keyvals:
1404 #should be of the form
1405 #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof
1406 #ORIGIN_URL_CONFIG: http://src.chromium.org/git
1407 RunGit(['config', keyvals['PUSH_URL_CONFIG'],
1408 keyvals['ORIGIN_URL_CONFIG']])
1409
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001410
joshua.lock@intel.com426f69b2012-08-02 23:41:49 +00001411def urlretrieve(source, destination):
1412 """urllib is broken for SSL connections via a proxy therefore we
1413 can't use urllib.urlretrieve()."""
1414 with open(destination, 'w') as f:
1415 f.write(urllib2.urlopen(source).read())
1416
1417
ukai@chromium.org712d6102013-11-27 00:52:58 +00001418def hasSheBang(fname):
1419 """Checks fname is a #! script."""
1420 with open(fname) as f:
1421 return f.read(2).startswith('#!')
1422
1423
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001424def DownloadHooks(force):
1425 """downloads hooks
1426
1427 Args:
1428 force: True to update hooks. False to install hooks if not present.
1429 """
1430 if not settings.GetIsGerrit():
1431 return
ukai@chromium.org712d6102013-11-27 00:52:58 +00001432 src = 'https://gerrit-review.googlesource.com/tools/hooks/commit-msg'
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001433 dst = os.path.join(settings.GetRoot(), '.git', 'hooks', 'commit-msg')
1434 if not os.access(dst, os.X_OK):
1435 if os.path.exists(dst):
1436 if not force:
1437 return
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001438 try:
joshua.lock@intel.com426f69b2012-08-02 23:41:49 +00001439 urlretrieve(src, dst)
ukai@chromium.org712d6102013-11-27 00:52:58 +00001440 if not hasSheBang(dst):
1441 DieWithError('Not a script: %s\n'
1442 'You need to download from\n%s\n'
1443 'into .git/hooks/commit-msg and '
1444 'chmod +x .git/hooks/commit-msg' % (dst, src))
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001445 os.chmod(dst, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
1446 except Exception:
1447 if os.path.exists(dst):
1448 os.remove(dst)
ukai@chromium.org712d6102013-11-27 00:52:58 +00001449 DieWithError('\nFailed to download hooks.\n'
1450 'You need to download from\n%s\n'
1451 'into .git/hooks/commit-msg and '
1452 'chmod +x .git/hooks/commit-msg' % src)
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001453
1454
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001455@subcommand.usage('[repo root containing codereview.settings]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001456def CMDconfig(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001457 """Edits configuration for this tree."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001458
pgervais@chromium.org87884cc2014-01-03 22:23:41 +00001459 parser.add_option('--activate-update', action='store_true',
1460 help='activate auto-updating [rietveld] section in '
1461 '.git/config')
1462 parser.add_option('--deactivate-update', action='store_true',
1463 help='deactivate auto-updating [rietveld] section in '
1464 '.git/config')
1465 options, args = parser.parse_args(args)
1466
1467 if options.deactivate_update:
1468 RunGit(['config', 'rietveld.autoupdate', 'false'])
1469 return
1470
1471 if options.activate_update:
1472 RunGit(['config', '--unset', 'rietveld.autoupdate'])
1473 return
1474
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001475 if len(args) == 0:
1476 GetCodereviewSettingsInteractively()
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001477 DownloadHooks(True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001478 return 0
1479
1480 url = args[0]
1481 if not url.endswith('codereview.settings'):
1482 url = os.path.join(url, 'codereview.settings')
1483
1484 # Load code review settings and download hooks (if available).
1485 LoadCodereviewSettingsFromFile(urllib2.urlopen(url))
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001486 DownloadHooks(True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001487 return 0
1488
1489
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001490def CMDbaseurl(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001491 """Gets or sets base-url for this branch."""
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001492 branchref = RunGit(['symbolic-ref', 'HEAD']).strip()
1493 branch = ShortBranchName(branchref)
1494 _, args = parser.parse_args(args)
1495 if not args:
1496 print("Current base-url:")
1497 return RunGit(['config', 'branch.%s.base-url' % branch],
1498 error_ok=False).strip()
1499 else:
1500 print("Setting base-url to %s" % args[0])
1501 return RunGit(['config', 'branch.%s.base-url' % branch, args[0]],
1502 error_ok=False).strip()
1503
1504
jsbell@chromium.orgb99fbd92014-09-11 17:29:28 +00001505def color_for_status(status):
1506 """Maps a Changelist status to color, for CMDstatus and other tools."""
1507 return {
1508 'unsent': Fore.RED,
1509 'waiting': Fore.BLUE,
1510 'reply': Fore.YELLOW,
1511 'lgtm': Fore.GREEN,
1512 'commit': Fore.MAGENTA,
1513 'closed': Fore.CYAN,
1514 'error': Fore.WHITE,
1515 }.get(status, Fore.WHITE)
1516
nodir@chromium.orga6de1f42015-06-10 04:23:17 +00001517def fetch_cl_status(branch, auth_config=None):
1518 """Fetches information for an issue and returns (branch, issue, status)."""
1519 cl = Changelist(branchref=branch, auth_config=auth_config)
1520 url = cl.GetIssueURL()
1521 status = cl.GetStatus()
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001522
nodir@chromium.orga6de1f42015-06-10 04:23:17 +00001523 if url and (not status or status == 'error'):
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001524 # The issue probably doesn't exist anymore.
nodir@chromium.orga6de1f42015-06-10 04:23:17 +00001525 url += ' (broken)'
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001526
nodir@chromium.orga6de1f42015-06-10 04:23:17 +00001527 return (branch, url, status)
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001528
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001529def get_cl_statuses(
1530 branches, fine_grained, max_processes=None, auth_config=None):
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001531 """Returns a blocking iterable of (branch, issue, color) for given branches.
1532
1533 If fine_grained is true, this will fetch CL statuses from the server.
1534 Otherwise, simply indicate if there's a matching url for the given branches.
1535
1536 If max_processes is specified, it is used as the maximum number of processes
1537 to spawn to fetch CL status from the server. Otherwise 1 process per branch is
1538 spawned.
1539 """
1540 # Silence upload.py otherwise it becomes unwieldly.
1541 upload.verbosity = 0
1542
1543 if fine_grained:
1544 # Process one branch synchronously to work through authentication, then
1545 # spawn processes to process all the other branches in parallel.
1546 if branches:
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001547 fetch = lambda branch: fetch_cl_status(branch, auth_config=auth_config)
1548 yield fetch(branches[0])
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001549
1550 branches_to_fetch = branches[1:]
1551 pool = ThreadPool(
1552 min(max_processes, len(branches_to_fetch))
1553 if max_processes is not None
1554 else len(branches_to_fetch))
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001555 for x in pool.imap_unordered(fetch, branches_to_fetch):
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001556 yield x
1557 else:
1558 # Do not use GetApprovingReviewers(), since it requires an HTTP request.
1559 for b in branches:
nodir@chromium.orga6de1f42015-06-10 04:23:17 +00001560 cl = Changelist(branchref=b, auth_config=auth_config)
1561 url = cl.GetIssueURL()
1562 yield (b, url, 'waiting' if url else 'error')
jsbell@chromium.orgb99fbd92014-09-11 17:29:28 +00001563
rmistry@google.com2dd99862015-06-22 12:22:18 +00001564
1565def upload_branch_deps(cl, args):
1566 """Uploads CLs of local branches that are dependents of the current branch.
1567
1568 If the local branch dependency tree looks like:
1569 test1 -> test2.1 -> test3.1
1570 -> test3.2
1571 -> test2.2 -> test3.3
1572
1573 and you run "git cl upload --dependencies" from test1 then "git cl upload" is
1574 run on the dependent branches in this order:
1575 test2.1, test3.1, test3.2, test2.2, test3.3
1576
1577 Note: This function does not rebase your local dependent branches. Use it when
1578 you make a change to the parent branch that will not conflict with its
1579 dependent branches, and you would like their dependencies updated in
1580 Rietveld.
1581 """
1582 if git_common.is_dirty_git_tree('upload-branch-deps'):
1583 return 1
1584
1585 root_branch = cl.GetBranch()
1586 if root_branch is None:
1587 DieWithError('Can\'t find dependent branches from detached HEAD state. '
1588 'Get on a branch!')
1589 if not cl.GetIssue() or not cl.GetPatchset():
1590 DieWithError('Current branch does not have an uploaded CL. We cannot set '
1591 'patchset dependencies without an uploaded CL.')
1592
1593 branches = RunGit(['for-each-ref',
1594 '--format=%(refname:short) %(upstream:short)',
1595 'refs/heads'])
1596 if not branches:
1597 print('No local branches found.')
1598 return 0
1599
1600 # Create a dictionary of all local branches to the branches that are dependent
1601 # on it.
1602 tracked_to_dependents = collections.defaultdict(list)
1603 for b in branches.splitlines():
1604 tokens = b.split()
1605 if len(tokens) == 2:
1606 branch_name, tracked = tokens
1607 tracked_to_dependents[tracked].append(branch_name)
1608
1609 print
1610 print 'The dependent local branches of %s are:' % root_branch
1611 dependents = []
1612 def traverse_dependents_preorder(branch, padding=''):
1613 dependents_to_process = tracked_to_dependents.get(branch, [])
1614 padding += ' '
1615 for dependent in dependents_to_process:
1616 print '%s%s' % (padding, dependent)
1617 dependents.append(dependent)
1618 traverse_dependents_preorder(dependent, padding)
1619 traverse_dependents_preorder(root_branch)
1620 print
1621
1622 if not dependents:
1623 print 'There are no dependent local branches for %s' % root_branch
1624 return 0
1625
1626 print ('This command will checkout all dependent branches and run '
1627 '"git cl upload".')
1628 ask_for_data('[Press enter to continue or ctrl-C to quit]')
1629
1630 # Add a default patchset title to all upload calls.
1631 args.extend(['-t', 'Updated patchset dependency'])
1632 # Record all dependents that failed to upload.
1633 failures = {}
1634 # Go through all dependents, checkout the branch and upload.
1635 try:
1636 for dependent_branch in dependents:
1637 print
1638 print '--------------------------------------'
1639 print 'Running "git cl upload" from %s:' % dependent_branch
1640 RunGit(['checkout', '-q', dependent_branch])
1641 print
1642 try:
1643 if CMDupload(OptionParser(), args) != 0:
1644 print 'Upload failed for %s!' % dependent_branch
1645 failures[dependent_branch] = 1
1646 except: # pylint: disable=W0702
1647 failures[dependent_branch] = 1
1648 print
1649 finally:
1650 # Swap back to the original root branch.
1651 RunGit(['checkout', '-q', root_branch])
1652
1653 print
1654 print 'Upload complete for dependent branches!'
1655 for dependent_branch in dependents:
1656 upload_status = 'failed' if failures.get(dependent_branch) else 'succeeded'
1657 print ' %s : %s' % (dependent_branch, upload_status)
1658 print
1659
1660 return 0
1661
1662
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001663def CMDstatus(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001664 """Show status of changelists.
1665
1666 Colors are used to tell the state of the CL unless --fast is used:
jsbell@chromium.orgaeab41a2013-12-10 20:01:22 +00001667 - Red not sent for review or broken
1668 - Blue waiting for review
1669 - Yellow waiting for you to reply to review
1670 - Green LGTM'ed
1671 - Magenta in the commit queue
1672 - Cyan was committed, branch can be deleted
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001673
1674 Also see 'git cl comments'.
1675 """
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001676 parser.add_option('--field',
1677 help='print only specific field (desc|id|patch|url)')
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001678 parser.add_option('-f', '--fast', action='store_true',
1679 help='Do not retrieve review status')
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001680 parser.add_option(
1681 '-j', '--maxjobs', action='store', type=int,
1682 help='The maximum number of jobs to use when retrieving review status')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001683
1684 auth.add_auth_options(parser)
1685 options, args = parser.parse_args(args)
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001686 if args:
1687 parser.error('Unsupported args: %s' % args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001688 auth_config = auth.extract_auth_config_from_options(options)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001689
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001690 if options.field:
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001691 cl = Changelist(auth_config=auth_config)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001692 if options.field.startswith('desc'):
1693 print cl.GetDescription()
1694 elif options.field == 'id':
1695 issueid = cl.GetIssue()
1696 if issueid:
1697 print issueid
1698 elif options.field == 'patch':
1699 patchset = cl.GetPatchset()
1700 if patchset:
1701 print patchset
1702 elif options.field == 'url':
1703 url = cl.GetIssueURL()
1704 if url:
1705 print url
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001706 return 0
1707
1708 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads'])
1709 if not branches:
1710 print('No local branch found.')
1711 return 0
1712
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001713 changes = (
1714 Changelist(branchref=b, auth_config=auth_config)
1715 for b in branches.splitlines())
jrobbins@chromium.orga4c03052014-04-25 19:06:36 +00001716 branches = [c.GetBranch() for c in changes]
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001717 alignment = max(5, max(len(b) for b in branches))
1718 print 'Branches associated with reviews:'
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001719 output = get_cl_statuses(branches,
1720 fine_grained=not options.fast,
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001721 max_processes=options.maxjobs,
1722 auth_config=auth_config)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001723
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001724 branch_statuses = {}
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001725 alignment = max(5, max(len(ShortBranchName(b)) for b in branches))
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001726 for branch in sorted(branches):
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001727 while branch not in branch_statuses:
nodir@chromium.orga6de1f42015-06-10 04:23:17 +00001728 b, i, status = output.next()
1729 branch_statuses[b] = (i, status)
1730 issue_url, status = branch_statuses.pop(branch)
1731 color = color_for_status(status)
maruel@chromium.org885f6512013-07-27 02:17:26 +00001732 reset = Fore.RESET
1733 if not sys.stdout.isatty():
1734 color = ''
1735 reset = ''
nodir@chromium.orga6de1f42015-06-10 04:23:17 +00001736 status_str = '(%s)' % status if status else ''
1737 print ' %*s : %s%s %s%s' % (
1738 alignment, ShortBranchName(branch), color, issue_url, status_str,
1739 reset)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001740
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001741 cl = Changelist(auth_config=auth_config)
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001742 print
1743 print 'Current branch:',
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001744 print cl.GetBranch()
dpranke@chromium.orgee87f582015-07-31 18:46:25 +00001745 if not cl.GetIssue():
1746 print 'No issue assigned.'
1747 return 0
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001748 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL())
maruel@chromium.org85616e02014-07-28 15:37:55 +00001749 if not options.fast:
1750 print 'Issue description:'
1751 print cl.GetDescription(pretty=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001752 return 0
1753
1754
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001755def colorize_CMDstatus_doc():
1756 """To be called once in main() to add colors to git cl status help."""
1757 colors = [i for i in dir(Fore) if i[0].isupper()]
1758
1759 def colorize_line(line):
1760 for color in colors:
1761 if color in line.upper():
1762 # Extract whitespaces first and the leading '-'.
1763 indent = len(line) - len(line.lstrip(' ')) + 1
1764 return line[:indent] + getattr(Fore, color) + line[indent:] + Fore.RESET
1765 return line
1766
1767 lines = CMDstatus.__doc__.splitlines()
1768 CMDstatus.__doc__ = '\n'.join(colorize_line(l) for l in lines)
1769
1770
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001771@subcommand.usage('[issue_number]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001772def CMDissue(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001773 """Sets or displays the current code review issue number.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001774
1775 Pass issue number 0 to clear the current issue.
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001776 """
dnj@chromium.org406c4402015-03-03 17:22:28 +00001777 parser.add_option('-r', '--reverse', action='store_true',
1778 help='Lookup the branch(es) for the specified issues. If '
1779 'no issues are specified, all branches with mapped '
1780 'issues will be listed.')
1781 options, args = parser.parse_args(args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001782
dnj@chromium.org406c4402015-03-03 17:22:28 +00001783 if options.reverse:
1784 branches = RunGit(['for-each-ref', 'refs/heads',
1785 '--format=%(refname:short)']).splitlines()
1786
1787 # Reverse issue lookup.
1788 issue_branch_map = {}
1789 for branch in branches:
1790 cl = Changelist(branchref=branch)
1791 issue_branch_map.setdefault(cl.GetIssue(), []).append(branch)
1792 if not args:
1793 args = sorted(issue_branch_map.iterkeys())
1794 for issue in args:
1795 if not issue:
1796 continue
1797 print 'Branch for issue number %s: %s' % (
1798 issue, ', '.join(issue_branch_map.get(int(issue)) or ('None',)))
1799 else:
1800 cl = Changelist()
1801 if len(args) > 0:
1802 try:
1803 issue = int(args[0])
1804 except ValueError:
1805 DieWithError('Pass a number to set the issue or none to list it.\n'
1806 'Maybe you want to run git cl status?')
1807 cl.SetIssue(issue)
1808 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001809 return 0
1810
1811
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001812def CMDcomments(parser, args):
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001813 """Shows or posts review comments for any changelist."""
1814 parser.add_option('-a', '--add-comment', dest='comment',
1815 help='comment to add to an issue')
1816 parser.add_option('-i', dest='issue',
1817 help="review issue id (defaults to current issue)")
smut@google.comc85ac942015-09-15 16:34:43 +00001818 parser.add_option('-j', '--json-file',
1819 help='File to write JSON summary to')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001820 auth.add_auth_options(parser)
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001821 options, args = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001822 auth_config = auth.extract_auth_config_from_options(options)
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001823
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001824 issue = None
1825 if options.issue:
1826 try:
1827 issue = int(options.issue)
1828 except ValueError:
1829 DieWithError('A review issue id is expected to be a number')
1830
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001831 cl = Changelist(issue=issue, auth_config=auth_config)
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001832
1833 if options.comment:
1834 cl.AddComment(options.comment)
1835 return 0
1836
1837 data = cl.GetIssueProperties()
smut@google.comc85ac942015-09-15 16:34:43 +00001838 summary = []
maruel@chromium.org5cab2d32014-11-11 18:32:41 +00001839 for message in sorted(data.get('messages', []), key=lambda x: x['date']):
smut@google.comc85ac942015-09-15 16:34:43 +00001840 summary.append({
1841 'date': message['date'],
1842 'lgtm': False,
1843 'message': message['text'],
1844 'not_lgtm': False,
1845 'sender': message['sender'],
1846 })
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001847 if message['disapproval']:
1848 color = Fore.RED
smut@google.comc85ac942015-09-15 16:34:43 +00001849 summary[-1]['not lgtm'] = True
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001850 elif message['approval']:
1851 color = Fore.GREEN
smut@google.comc85ac942015-09-15 16:34:43 +00001852 summary[-1]['lgtm'] = True
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001853 elif message['sender'] == data['owner_email']:
1854 color = Fore.MAGENTA
1855 else:
1856 color = Fore.BLUE
1857 print '\n%s%s %s%s' % (
1858 color, message['date'].split('.', 1)[0], message['sender'],
1859 Fore.RESET)
1860 if message['text'].strip():
1861 print '\n'.join(' ' + l for l in message['text'].splitlines())
smut@google.comc85ac942015-09-15 16:34:43 +00001862 if options.json_file:
1863 with open(options.json_file, 'wb') as f:
1864 json.dump(summary, f)
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001865 return 0
1866
1867
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001868def CMDdescription(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001869 """Brings up the editor for the current CL's description."""
smut@google.com34fb6b12015-07-13 20:03:26 +00001870 parser.add_option('-d', '--display', action='store_true',
1871 help='Display the description instead of opening an editor')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001872 auth.add_auth_options(parser)
1873 options, _ = parser.parse_args(args)
1874 auth_config = auth.extract_auth_config_from_options(options)
1875 cl = Changelist(auth_config=auth_config)
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001876 if not cl.GetIssue():
1877 DieWithError('This branch has no associated changelist.')
1878 description = ChangeDescription(cl.GetDescription())
smut@google.com34fb6b12015-07-13 20:03:26 +00001879 if options.display:
1880 print description.description
1881 return 0
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001882 description.prompt()
wychen@chromium.org063e4e52015-04-03 06:51:44 +00001883 if cl.GetDescription() != description.description:
1884 cl.UpdateDescription(description.description)
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001885 return 0
1886
1887
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001888def CreateDescriptionFromLog(args):
1889 """Pulls out the commit log to use as a base for the CL description."""
1890 log_args = []
1891 if len(args) == 1 and not args[0].endswith('.'):
1892 log_args = [args[0] + '..']
1893 elif len(args) == 1 and args[0].endswith('...'):
1894 log_args = [args[0][:-1]]
1895 elif len(args) == 2:
1896 log_args = [args[0] + '..' + args[1]]
1897 else:
1898 log_args = args[:] # Hope for the best!
maruel@chromium.org373af802012-05-25 21:07:33 +00001899 return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001900
1901
thestig@chromium.org44202a22014-03-11 19:22:18 +00001902def CMDlint(parser, args):
1903 """Runs cpplint on the current changelist."""
tzik@chromium.orgf204d4b2014-03-13 07:40:55 +00001904 parser.add_option('--filter', action='append', metavar='-x,+y',
1905 help='Comma-separated list of cpplint\'s category-filters')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001906 auth.add_auth_options(parser)
1907 options, args = parser.parse_args(args)
1908 auth_config = auth.extract_auth_config_from_options(options)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001909
1910 # Access to a protected member _XX of a client class
1911 # pylint: disable=W0212
1912 try:
1913 import cpplint
1914 import cpplint_chromium
1915 except ImportError:
1916 print "Your depot_tools is missing cpplint.py and/or cpplint_chromium.py."
1917 return 1
1918
1919 # Change the current working directory before calling lint so that it
1920 # shows the correct base.
1921 previous_cwd = os.getcwd()
1922 os.chdir(settings.GetRoot())
1923 try:
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001924 cl = Changelist(auth_config=auth_config)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001925 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
1926 files = [f.LocalPath() for f in change.AffectedFiles()]
thestig@chromium.org5839eb52014-05-30 16:20:51 +00001927 if not files:
1928 print "Cannot lint an empty CL"
1929 return 1
thestig@chromium.org44202a22014-03-11 19:22:18 +00001930
1931 # Process cpplints arguments if any.
tzik@chromium.orgf204d4b2014-03-13 07:40:55 +00001932 command = args + files
1933 if options.filter:
1934 command = ['--filter=' + ','.join(options.filter)] + command
1935 filenames = cpplint.ParseArguments(command)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001936
1937 white_regex = re.compile(settings.GetLintRegex())
1938 black_regex = re.compile(settings.GetLintIgnoreRegex())
1939 extra_check_functions = [cpplint_chromium.CheckPointerDeclarationWhitespace]
1940 for filename in filenames:
1941 if white_regex.match(filename):
1942 if black_regex.match(filename):
1943 print "Ignoring file %s" % filename
1944 else:
1945 cpplint.ProcessFile(filename, cpplint._cpplint_state.verbose_level,
1946 extra_check_functions)
1947 else:
1948 print "Skipping file %s" % filename
1949 finally:
1950 os.chdir(previous_cwd)
1951 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count
1952 if cpplint._cpplint_state.error_count != 0:
1953 return 1
1954 return 0
1955
1956
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001957def CMDpresubmit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001958 """Runs presubmit tests on the current changelist."""
ilevy@chromium.org375a9022013-01-07 01:12:05 +00001959 parser.add_option('-u', '--upload', action='store_true',
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001960 help='Run upload hook instead of the push/dcommit hook')
ilevy@chromium.org375a9022013-01-07 01:12:05 +00001961 parser.add_option('-f', '--force', action='store_true',
sbc@chromium.org495ad152012-09-04 23:07:42 +00001962 help='Run checks even if tree is dirty')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001963 auth.add_auth_options(parser)
1964 options, args = parser.parse_args(args)
1965 auth_config = auth.extract_auth_config_from_options(options)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001966
sbc@chromium.org71437c02015-04-09 19:29:40 +00001967 if not options.force and git_common.is_dirty_git_tree('presubmit'):
ukai@chromium.org259e4682012-10-25 07:36:33 +00001968 print 'use --force to check even if tree is dirty.'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001969 return 1
1970
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001971 cl = Changelist(auth_config=auth_config)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001972 if args:
1973 base_branch = args[0]
1974 else:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00001975 # Default to diffing against the common ancestor of the upstream branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001976 base_branch = cl.GetCommonAncestorWithUpstream()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001977
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00001978 cl.RunHook(
1979 committing=not options.upload,
1980 may_prompt=False,
1981 verbose=options.verbose,
1982 change=cl.GetChange(base_branch, None))
dpranke@chromium.org0a2bb372011-03-25 01:16:22 +00001983 return 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001984
1985
sivachandra@chromium.orgaebe87f2012-10-22 20:34:21 +00001986def AddChangeIdToCommitMessage(options, args):
1987 """Re-commits using the current message, assumes the commit hook is in
1988 place.
1989 """
1990 log_desc = options.message or CreateDescriptionFromLog(args)
1991 git_command = ['commit', '--amend', '-m', log_desc]
1992 RunGit(git_command)
1993 new_log_desc = CreateDescriptionFromLog(args)
1994 if CHANGE_ID in new_log_desc:
1995 print 'git-cl: Added Change-Id to commit message.'
1996 else:
1997 print >> sys.stderr, 'ERROR: Gerrit commit-msg hook not available.'
1998
1999
piman@chromium.org336f9122014-09-04 02:16:55 +00002000def GerritUpload(options, args, cl, change):
ukai@chromium.orge8077812012-02-03 03:41:46 +00002001 """upload the current branch to gerrit."""
2002 # We assume the remote called "origin" is the one we want.
2003 # It is probably not worthwhile to support different workflows.
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002004 gerrit_remote = 'origin'
luqui@chromium.org609f3952015-05-04 22:47:04 +00002005
2006 remote, remote_branch = cl.GetRemoteBranch()
2007 branch = GetTargetRef(remote, remote_branch, options.target_branch,
2008 pending_prefix='')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002009
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002010 change_desc = ChangeDescription(
2011 options.message or CreateDescriptionFromLog(args))
2012 if not change_desc.description:
ukai@chromium.orge8077812012-02-03 03:41:46 +00002013 print "Description is empty; aborting."
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002014 return 1
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00002015
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002016 if options.squash:
2017 # Try to get the message from a previous upload.
2018 shadow_branch = 'refs/heads/git_cl_uploads/' + cl.GetBranch()
2019 message = RunGitSilent(['show', '--format=%s\n\n%b', '-s', shadow_branch])
2020 if not message:
2021 if not options.force:
2022 change_desc.prompt()
2023
2024 if CHANGE_ID not in change_desc.description:
2025 # Run the commit-msg hook without modifying the head commit by writing
2026 # the commit message to a temporary file and running the hook over it,
2027 # then reading the file back in.
2028 commit_msg_hook = os.path.join(settings.GetRoot(), '.git', 'hooks',
2029 'commit-msg')
2030 file_handle, msg_file = tempfile.mkstemp(text=True,
2031 prefix='commit_msg')
2032 try:
2033 try:
2034 with os.fdopen(file_handle, 'w') as fileobj:
2035 fileobj.write(change_desc.description)
2036 finally:
2037 os.close(file_handle)
2038 RunCommand([commit_msg_hook, msg_file])
2039 change_desc.set_description(gclient_utils.FileRead(msg_file))
2040 finally:
2041 os.remove(msg_file)
2042
2043 if not change_desc.description:
2044 print "Description is empty; aborting."
2045 return 1
2046
2047 message = change_desc.description
2048
2049 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
2050 if remote is '.':
2051 # If our upstream branch is local, we base our squashed commit on its
2052 # squashed version.
2053 parent = ('refs/heads/git_cl_uploads/' +
2054 scm.GIT.ShortBranchName(upstream_branch))
2055
2056 # Verify that the upstream branch has been uploaded too, otherwise Gerrit
2057 # will create additional CLs when uploading.
2058 if (RunGitSilent(['rev-parse', upstream_branch + ':']) !=
2059 RunGitSilent(['rev-parse', parent + ':'])):
2060 print 'Upload upstream branch ' + upstream_branch + ' first.'
2061 return 1
2062 else:
2063 parent = cl.GetCommonAncestorWithUpstream()
2064
2065 tree = RunGit(['rev-parse', 'HEAD:']).strip()
2066 ref_to_push = RunGit(['commit-tree', tree, '-p', parent,
2067 '-m', message]).strip()
2068 else:
2069 if CHANGE_ID not in change_desc.description:
2070 AddChangeIdToCommitMessage(options, args)
2071 ref_to_push = 'HEAD'
2072 parent = '%s/%s' % (gerrit_remote, branch)
2073
2074 commits = RunGitSilent(['rev-list', '%s..%s' % (parent,
2075 ref_to_push)]).splitlines()
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00002076 if len(commits) > 1:
2077 print('WARNING: This will upload %d commits. Run the following command '
2078 'to see which commits will be uploaded: ' % len(commits))
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002079 print('git log %s..%s' % (parent, ref_to_push))
2080 print('You can also use `git squash-branch` to squash these into a single '
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00002081 'commit.')
2082 ask_for_data('About to upload; enter to confirm.')
2083
piman@chromium.org336f9122014-09-04 02:16:55 +00002084 if options.reviewers or options.tbr_owners:
2085 change_desc.update_reviewers(options.reviewers, options.tbr_owners, change)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002086
ukai@chromium.orge8077812012-02-03 03:41:46 +00002087 receive_options = []
2088 cc = cl.GetCCList().split(',')
2089 if options.cc:
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002090 cc.extend(options.cc)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002091 cc = filter(None, cc)
2092 if cc:
2093 receive_options += ['--cc=' + email for email in cc]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002094 if change_desc.get_reviewers():
2095 receive_options.extend(
2096 '--reviewer=' + email for email in change_desc.get_reviewers())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002097
ukai@chromium.orge8077812012-02-03 03:41:46 +00002098 git_command = ['push']
2099 if receive_options:
ukai@chromium.org19bbfa22012-02-03 16:18:11 +00002100 git_command.append('--receive-pack=git receive-pack %s' %
ukai@chromium.orge8077812012-02-03 03:41:46 +00002101 ' '.join(receive_options))
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002102 git_command += [gerrit_remote, ref_to_push + ':refs/for/' + branch]
ukai@chromium.orge8077812012-02-03 03:41:46 +00002103 RunGit(git_command)
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002104
2105 if options.squash:
2106 head = RunGit(['rev-parse', 'HEAD']).strip()
2107 RunGit(['update-ref', '-m', 'Uploaded ' + head, shadow_branch, ref_to_push])
2108
ukai@chromium.orge8077812012-02-03 03:41:46 +00002109 # TODO(ukai): parse Change-Id: and set issue number?
2110 return 0
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002111
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002112
wittman@chromium.org455dc922015-01-26 20:15:50 +00002113def GetTargetRef(remote, remote_branch, target_branch, pending_prefix):
2114 """Computes the remote branch ref to use for the CL.
2115
2116 Args:
2117 remote (str): The git remote for the CL.
2118 remote_branch (str): The git remote branch for the CL.
2119 target_branch (str): The target branch specified by the user.
2120 pending_prefix (str): The pending prefix from the settings.
2121 """
2122 if not (remote and remote_branch):
2123 return None
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002124
wittman@chromium.org455dc922015-01-26 20:15:50 +00002125 if target_branch:
2126 # Cannonicalize branch references to the equivalent local full symbolic
2127 # refs, which are then translated into the remote full symbolic refs
2128 # below.
2129 if '/' not in target_branch:
2130 remote_branch = 'refs/remotes/%s/%s' % (remote, target_branch)
2131 else:
2132 prefix_replacements = (
2133 ('^((refs/)?remotes/)?branch-heads/', 'refs/remotes/branch-heads/'),
2134 ('^((refs/)?remotes/)?%s/' % remote, 'refs/remotes/%s/' % remote),
2135 ('^(refs/)?heads/', 'refs/remotes/%s/' % remote),
2136 )
2137 match = None
2138 for regex, replacement in prefix_replacements:
2139 match = re.search(regex, target_branch)
2140 if match:
2141 remote_branch = target_branch.replace(match.group(0), replacement)
2142 break
2143 if not match:
2144 # This is a branch path but not one we recognize; use as-is.
2145 remote_branch = target_branch
rmistry@google.comc68112d2015-03-03 12:48:06 +00002146 elif remote_branch in REFS_THAT_ALIAS_TO_OTHER_REFS:
2147 # Handle the refs that need to land in different refs.
2148 remote_branch = REFS_THAT_ALIAS_TO_OTHER_REFS[remote_branch]
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002149
wittman@chromium.org455dc922015-01-26 20:15:50 +00002150 # Create the true path to the remote branch.
2151 # Does the following translation:
2152 # * refs/remotes/origin/refs/diff/test -> refs/diff/test
2153 # * refs/remotes/origin/master -> refs/heads/master
2154 # * refs/remotes/branch-heads/test -> refs/branch-heads/test
2155 if remote_branch.startswith('refs/remotes/%s/refs/' % remote):
2156 remote_branch = remote_branch.replace('refs/remotes/%s/' % remote, '')
2157 elif remote_branch.startswith('refs/remotes/%s/' % remote):
2158 remote_branch = remote_branch.replace('refs/remotes/%s/' % remote,
2159 'refs/heads/')
2160 elif remote_branch.startswith('refs/remotes/branch-heads'):
2161 remote_branch = remote_branch.replace('refs/remotes/', 'refs/')
2162 # If a pending prefix exists then replace refs/ with it.
2163 if pending_prefix:
2164 remote_branch = remote_branch.replace('refs/', pending_prefix)
2165 return remote_branch
2166
2167
piman@chromium.org336f9122014-09-04 02:16:55 +00002168def RietveldUpload(options, args, cl, change):
ukai@chromium.orge8077812012-02-03 03:41:46 +00002169 """upload the patch to rietveld."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002170 upload_args = ['--assume_yes'] # Don't ask about untracked files.
2171 upload_args.extend(['--server', cl.GetRietveldServer()])
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002172 upload_args.extend(auth.auth_config_to_command_options(cl.auth_config))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002173 if options.emulate_svn_auto_props:
2174 upload_args.append('--emulate_svn_auto_props')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002175
2176 change_desc = None
2177
pgervais@chromium.org91141372014-01-09 23:27:20 +00002178 if options.email is not None:
2179 upload_args.extend(['--email', options.email])
2180
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002181 if cl.GetIssue():
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00002182 if options.title:
2183 upload_args.extend(['--title', options.title])
rogerta@chromium.orgafadfca2013-05-29 14:15:53 +00002184 if options.message:
2185 upload_args.extend(['--message', options.message])
maruel@chromium.org52424302012-08-29 15:14:30 +00002186 upload_args.extend(['--issue', str(cl.GetIssue())])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002187 print ("This branch is associated with issue %s. "
2188 "Adding patch to that issue." % cl.GetIssue())
2189 else:
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00002190 if options.title:
2191 upload_args.extend(['--title', options.title])
rogerta@chromium.org43e34f02013-03-25 14:52:48 +00002192 message = options.title or options.message or CreateDescriptionFromLog(args)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002193 change_desc = ChangeDescription(message)
piman@chromium.org336f9122014-09-04 02:16:55 +00002194 if options.reviewers or options.tbr_owners:
2195 change_desc.update_reviewers(options.reviewers,
2196 options.tbr_owners,
2197 change)
maruel@chromium.org71e12a92012-02-14 02:34:15 +00002198 if not options.force:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002199 change_desc.prompt()
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002200
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002201 if not change_desc.description:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002202 print "Description is empty; aborting."
2203 return 1
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002204
maruel@chromium.org71e12a92012-02-14 02:34:15 +00002205 upload_args.extend(['--message', change_desc.description])
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002206 if change_desc.get_reviewers():
2207 upload_args.append('--reviewers=' + ','.join(change_desc.get_reviewers()))
maruel@chromium.orga3353652011-11-30 14:26:57 +00002208 if options.send_mail:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002209 if not change_desc.get_reviewers():
maruel@chromium.orga3353652011-11-30 14:26:57 +00002210 DieWithError("Must specify reviewers to send email.")
2211 upload_args.append('--send_mail')
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +00002212
2213 # We check this before applying rietveld.private assuming that in
2214 # rietveld.cc only addresses which we can send private CLs to are listed
2215 # if rietveld.private is set, and so we should ignore rietveld.cc only when
2216 # --private is specified explicitly on the command line.
2217 if options.private:
2218 logging.warn('rietveld.cc is ignored since private flag is specified. '
2219 'You need to review and add them manually if necessary.')
2220 cc = cl.GetCCListWithoutDefault()
2221 else:
2222 cc = cl.GetCCList()
2223 cc = ','.join(filter(None, (cc, ','.join(options.cc))))
maruel@chromium.orgb2a7c332011-02-25 20:30:37 +00002224 if cc:
2225 upload_args.extend(['--cc', cc])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002226
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00002227 if options.private or settings.GetDefaultPrivateFlag() == "True":
2228 upload_args.append('--private')
2229
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002230 upload_args.extend(['--git_similarity', str(options.similarity)])
iannucci@chromium.org79540052012-10-19 23:15:26 +00002231 if not options.find_copies:
2232 upload_args.extend(['--git_no_find_copies'])
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002233
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002234 # Include the upstream repo's URL in the change -- this is useful for
2235 # projects that have their source spread across multiple repos.
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00002236 remote_url = cl.GetGitBaseUrlFromConfig()
2237 if not remote_url:
2238 if settings.GetIsGitSvn():
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00002239 remote_url = cl.GetGitSvnRemoteUrl()
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00002240 else:
jam@chromium.org80c51ae2014-10-17 18:43:02 +00002241 if cl.GetRemoteUrl() and '/' in cl.GetUpstreamBranch():
2242 remote_url = (cl.GetRemoteUrl() + '@'
2243 + cl.GetUpstreamBranch().split('/')[-1])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002244 if remote_url:
2245 upload_args.extend(['--base_url', remote_url])
rmistry@google.comd1e37582014-12-10 20:58:24 +00002246 remote, remote_branch = cl.GetRemoteBranch()
wittman@chromium.org455dc922015-01-26 20:15:50 +00002247 target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
2248 settings.GetPendingRefPrefix())
2249 if target_ref:
2250 upload_args.extend(['--target_ref', target_ref])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002251
rmistry@google.comd91b7e32015-06-23 11:24:07 +00002252 # Look for dependent patchsets. See crbug.com/480453 for more details.
2253 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
2254 upstream_branch = ShortBranchName(upstream_branch)
2255 if remote is '.':
2256 # A local branch is being tracked.
2257 local_branch = ShortBranchName(upstream_branch)
rmistry@google.com78948ed2015-07-08 23:09:57 +00002258 if settings.GetIsSkipDependencyUpload(local_branch):
rmistry@google.comd91b7e32015-06-23 11:24:07 +00002259 print
rmistry@google.com78948ed2015-07-08 23:09:57 +00002260 print ('Skipping dependency patchset upload because git config '
2261 'branch.%s.skip-deps-uploads is set to True.' % local_branch)
rmistry@google.comd91b7e32015-06-23 11:24:07 +00002262 print
rmistry@google.com78948ed2015-07-08 23:09:57 +00002263 else:
2264 auth_config = auth.extract_auth_config_from_options(options)
2265 branch_cl = Changelist(branchref=local_branch, auth_config=auth_config)
2266 branch_cl_issue_url = branch_cl.GetIssueURL()
2267 branch_cl_issue = branch_cl.GetIssue()
2268 branch_cl_patchset = branch_cl.GetPatchset()
2269 if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset:
2270 upload_args.extend(
2271 ['--depends_on_patchset', '%s:%s' % (
2272 branch_cl_issue, branch_cl_patchset)])
2273 print
2274 print ('The current branch (%s) is tracking a local branch (%s) with '
2275 'an associated CL.') % (cl.GetBranch(), local_branch)
2276 print 'Adding %s/#ps%s as a dependency patchset.' % (
2277 branch_cl_issue_url, branch_cl_patchset)
2278 print
rmistry@google.comd91b7e32015-06-23 11:24:07 +00002279
sheyang@chromium.org152cf832014-06-11 21:37:49 +00002280 project = settings.GetProject()
2281 if project:
2282 upload_args.extend(['--project', project])
2283
rmistry@google.comef966222015-04-07 11:15:01 +00002284 if options.cq_dry_run:
2285 upload_args.extend(['--cq_dry_run'])
2286
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002287 try:
ilevy@chromium.org82880192012-11-26 15:41:57 +00002288 upload_args = ['upload'] + upload_args + args
2289 logging.info('upload.RealMain(%s)', upload_args)
2290 issue, patchset = upload.RealMain(upload_args)
maruel@chromium.org911fce12013-07-29 23:01:13 +00002291 issue = int(issue)
2292 patchset = int(patchset)
maruel@chromium.org9ce0dff2011-04-04 17:56:50 +00002293 except KeyboardInterrupt:
2294 sys.exit(1)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002295 except:
2296 # If we got an exception after the user typed a description for their
2297 # change, back up the description before re-raising.
2298 if change_desc:
2299 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
2300 print '\nGot exception while uploading -- saving description to %s\n' \
2301 % backup_path
2302 backup_file = open(backup_path, 'w')
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002303 backup_file.write(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002304 backup_file.close()
2305 raise
2306
2307 if not cl.GetIssue():
2308 cl.SetIssue(issue)
2309 cl.SetPatchset(patchset)
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002310
2311 if options.use_commit_queue:
2312 cl.SetFlag('commit', '1')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002313 return 0
2314
2315
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002316def cleanup_list(l):
2317 """Fixes a list so that comma separated items are put as individual items.
2318
2319 So that "--reviewers joe@c,john@c --reviewers joa@c" results in
2320 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']).
2321 """
2322 items = sum((i.split(',') for i in l), [])
2323 stripped_items = (i.strip() for i in items)
2324 return sorted(filter(None, stripped_items))
2325
2326
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002327@subcommand.usage('[args to "git diff"]')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002328def CMDupload(parser, args):
rmistry@google.com78948ed2015-07-08 23:09:57 +00002329 """Uploads the current changelist to codereview.
2330
2331 Can skip dependency patchset uploads for a branch by running:
2332 git config branch.branch_name.skip-deps-uploads True
2333 To unset run:
2334 git config --unset branch.branch_name.skip-deps-uploads
2335 Can also set the above globally by using the --global flag.
2336 """
ukai@chromium.orge8077812012-02-03 03:41:46 +00002337 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
2338 help='bypass upload presubmit hook')
brettw@chromium.orgb65c43c2013-06-10 22:04:49 +00002339 parser.add_option('--bypass-watchlists', action='store_true',
2340 dest='bypass_watchlists',
2341 help='bypass watchlists auto CC-ing reviewers')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002342 parser.add_option('-f', action='store_true', dest='force',
2343 help="force yes to questions (don't prompt)")
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00002344 parser.add_option('-m', dest='message', help='message for patchset')
2345 parser.add_option('-t', dest='title', help='title for patchset')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002346 parser.add_option('-r', '--reviewers',
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002347 action='append', default=[],
ukai@chromium.orge8077812012-02-03 03:41:46 +00002348 help='reviewer email addresses')
2349 parser.add_option('--cc',
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002350 action='append', default=[],
ukai@chromium.orge8077812012-02-03 03:41:46 +00002351 help='cc email addresses')
adamk@chromium.org36f47302013-04-05 01:08:31 +00002352 parser.add_option('-s', '--send-mail', action='store_true',
ukai@chromium.orge8077812012-02-03 03:41:46 +00002353 help='send email to reviewer immediately')
pgervais@chromium.orgb9f27512014-08-08 15:52:33 +00002354 parser.add_option('--emulate_svn_auto_props',
2355 '--emulate-svn-auto-props',
2356 action="store_true",
ukai@chromium.orge8077812012-02-03 03:41:46 +00002357 dest="emulate_svn_auto_props",
2358 help="Emulate Subversion's auto properties feature.")
ukai@chromium.orge8077812012-02-03 03:41:46 +00002359 parser.add_option('-c', '--use-commit-queue', action='store_true',
2360 help='tell the commit queue to commit this patchset')
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00002361 parser.add_option('--private', action='store_true',
2362 help='set the review private (rietveld only)')
ukai@chromium.org8ef7ab22012-11-28 04:24:52 +00002363 parser.add_option('--target_branch',
pgervais@chromium.orgb9f27512014-08-08 15:52:33 +00002364 '--target-branch',
wittman@chromium.org455dc922015-01-26 20:15:50 +00002365 metavar='TARGET',
2366 help='Apply CL to remote ref TARGET. ' +
2367 'Default: remote branch head, or master')
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002368 parser.add_option('--squash', action='store_true',
2369 help='Squash multiple commits into one (Gerrit only)')
bauerb@chromium.org54b400c2016-01-14 10:08:25 +00002370 parser.add_option('--no-squash', action='store_true',
2371 help='Don\'t squash multiple commits into one ' +
2372 '(Gerrit only)')
pgervais@chromium.org91141372014-01-09 23:27:20 +00002373 parser.add_option('--email', default=None,
2374 help='email address to use to connect to Rietveld')
piman@chromium.org336f9122014-09-04 02:16:55 +00002375 parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true',
2376 help='add a set of OWNERS to TBR')
tandrii@chromium.orgd50452a2015-11-23 16:38:15 +00002377 parser.add_option('-d', '--cq-dry-run', dest='cq_dry_run',
2378 action='store_true',
rmistry@google.comef966222015-04-07 11:15:01 +00002379 help='Send the patchset to do a CQ dry run right after '
2380 'upload.')
rmistry@google.com2dd99862015-06-22 12:22:18 +00002381 parser.add_option('--dependencies', action='store_true',
2382 help='Uploads CLs of all the local branches that depend on '
2383 'the current branch')
pgervais@chromium.org91141372014-01-09 23:27:20 +00002384
rmistry@google.com2dd99862015-06-22 12:22:18 +00002385 orig_args = args
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002386 add_git_similarity(parser)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002387 auth.add_auth_options(parser)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002388 (options, args) = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002389 auth_config = auth.extract_auth_config_from_options(options)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002390
sbc@chromium.org71437c02015-04-09 19:29:40 +00002391 if git_common.is_dirty_git_tree('upload'):
ukai@chromium.orge8077812012-02-03 03:41:46 +00002392 return 1
2393
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002394 options.reviewers = cleanup_list(options.reviewers)
2395 options.cc = cleanup_list(options.cc)
2396
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002397 cl = Changelist(auth_config=auth_config)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002398 if args:
2399 # TODO(ukai): is it ok for gerrit case?
2400 base_branch = args[0]
2401 else:
luqui@chromium.org64e14362015-01-07 00:29:29 +00002402 if cl.GetBranch() is None:
2403 DieWithError('Can\'t upload from detached HEAD state. Get on a branch!')
2404
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00002405 # Default to diffing against common ancestor of upstream branch
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002406 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org5e07e062013-02-28 23:55:44 +00002407 args = [base_branch, 'HEAD']
ukai@chromium.orge8077812012-02-03 03:41:46 +00002408
vadimsh@chromium.orgeed4df32015-04-10 21:30:20 +00002409 # Make sure authenticated to Rietveld before running expensive hooks. It is
2410 # a fast, best efforts check. Rietveld still can reject the authentication
2411 # during the actual upload.
2412 if not settings.GetIsGerrit() and auth_config.use_oauth2:
2413 authenticator = auth.get_authenticator_for_host(
2414 cl.GetRietveldServer(), auth_config)
2415 if not authenticator.has_cached_credentials():
2416 raise auth.LoginRequiredError(cl.GetRietveldServer())
2417
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002418 # Apply watchlists on upload.
2419 change = cl.GetChange(base_branch, None)
2420 watchlist = watchlists.Watchlists(change.RepositoryRoot())
2421 files = [f.LocalPath() for f in change.AffectedFiles()]
brettw@chromium.orgb65c43c2013-06-10 22:04:49 +00002422 if not options.bypass_watchlists:
2423 cl.SetWatchers(watchlist.GetWatchersForPaths(files))
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002424
ukai@chromium.orge8077812012-02-03 03:41:46 +00002425 if not options.bypass_hooks:
piman@chromium.org336f9122014-09-04 02:16:55 +00002426 if options.reviewers or options.tbr_owners:
isherman@chromium.orgb5cded62014-03-25 17:47:57 +00002427 # Set the reviewer list now so that presubmit checks can access it.
2428 change_description = ChangeDescription(change.FullDescriptionText())
piman@chromium.org336f9122014-09-04 02:16:55 +00002429 change_description.update_reviewers(options.reviewers,
2430 options.tbr_owners,
2431 change)
isherman@chromium.orgb5cded62014-03-25 17:47:57 +00002432 change.SetDescriptionText(change_description.description)
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002433 hook_results = cl.RunHook(committing=False,
ukai@chromium.orge8077812012-02-03 03:41:46 +00002434 may_prompt=not options.force,
2435 verbose=options.verbose,
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002436 change=change)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002437 if not hook_results.should_continue():
2438 return 1
2439 if not options.reviewers and hook_results.reviewers:
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002440 options.reviewers = hook_results.reviewers.split(',')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002441
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002442 if cl.GetIssue():
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002443 latest_patchset = cl.GetMostRecentPatchset()
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002444 local_patchset = cl.GetPatchset()
dmikurube@chromium.org07d149f2013-04-03 11:40:23 +00002445 if latest_patchset and local_patchset and local_patchset != latest_patchset:
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002446 print ('The last upload made from this repository was patchset #%d but '
2447 'the most recent patchset on the server is #%d.'
2448 % (local_patchset, latest_patchset))
koz@chromium.orgc7192782013-04-09 23:28:46 +00002449 print ('Uploading will still work, but if you\'ve uploaded to this issue '
2450 'from another machine or branch the patch you\'re uploading now '
2451 'might not include those changes.')
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002452 ask_for_data('About to upload; enter to confirm.')
2453
iannucci@chromium.org79540052012-10-19 23:15:26 +00002454 print_stats(options.similarity, options.find_copies, args)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002455 if settings.GetIsGerrit():
bauerb@chromium.org54b400c2016-01-14 10:08:25 +00002456 if options.squash and options.no_squash:
2457 DieWithError('Can only use one of --squash or --no-squash')
2458
2459 options.squash = ((settings.GetSquashGerritUploads() or options.squash) and
2460 not options.no_squash)
2461
piman@chromium.org336f9122014-09-04 02:16:55 +00002462 return GerritUpload(options, args, cl, change)
2463 ret = RietveldUpload(options, args, cl, change)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00002464 if not ret:
rogerta@chromium.org4a6cd042013-04-12 15:40:42 +00002465 git_set_branch_value('last-upload-hash',
2466 RunGit(['rev-parse', 'HEAD']).strip())
rmistry@google.com5626a922015-02-26 14:03:30 +00002467 # Run post upload hooks, if specified.
2468 if settings.GetRunPostUploadHook():
2469 presubmit_support.DoPostUploadExecuter(
2470 change,
2471 cl,
2472 settings.GetRoot(),
2473 options.verbose,
2474 sys.stdout)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00002475
rmistry@google.com2dd99862015-06-22 12:22:18 +00002476 # Upload all dependencies if specified.
2477 if options.dependencies:
2478 print
2479 print '--dependencies has been specified.'
2480 print 'All dependent local branches will be re-uploaded.'
2481 print
2482 # Remove the dependencies flag from args so that we do not end up in a
2483 # loop.
2484 orig_args.remove('--dependencies')
2485 upload_branch_deps(cl, orig_args)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00002486 return ret
ukai@chromium.orge8077812012-02-03 03:41:46 +00002487
2488
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002489def IsSubmoduleMergeCommit(ref):
2490 # When submodules are added to the repo, we expect there to be a single
2491 # non-git-svn merge commit at remote HEAD with a signature comment.
2492 pattern = '^SVN changes up to revision [0-9]*$'
szager@chromium.orge84b7542012-06-15 21:26:58 +00002493 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref]
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002494 return RunGit(cmd) != ''
2495
2496
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002497def SendUpstream(parser, args, cmd):
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00002498 """Common code for CMDland and CmdDCommit
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002499
iannucci@chromium.org5724c962014-04-11 09:32:56 +00002500 Squashes branch into a single commit.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002501 Updates changelog with metadata (e.g. pointer to review).
2502 Pushes/dcommits the code upstream.
2503 Updates review and closes.
2504 """
2505 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
2506 help='bypass upload presubmit hook')
2507 parser.add_option('-m', dest='message',
2508 help="override review description")
2509 parser.add_option('-f', action='store_true', dest='force',
2510 help="force yes to questions (don't prompt)")
2511 parser.add_option('-c', dest='contributor',
2512 help="external contributor for patch (appended to " +
2513 "description and used as author for git). Should be " +
2514 "formatted as 'First Last <email@example.com>'")
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002515 add_git_similarity(parser)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002516 auth.add_auth_options(parser)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002517 (options, args) = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002518 auth_config = auth.extract_auth_config_from_options(options)
2519
2520 cl = Changelist(auth_config=auth_config)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002521
iannucci@chromium.org5724c962014-04-11 09:32:56 +00002522 current = cl.GetBranch()
2523 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
2524 if not settings.GetIsGitSvn() and remote == '.':
2525 print
2526 print 'Attempting to push branch %r into another local branch!' % current
2527 print
2528 print 'Either reparent this branch on top of origin/master:'
2529 print ' git reparent-branch --root'
2530 print
2531 print 'OR run `git rebase-update` if you think the parent branch is already'
2532 print 'committed.'
2533 print
2534 print ' Current parent: %r' % upstream_branch
2535 return 1
2536
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002537 if not args or cmd == 'land':
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002538 # Default to merging against our best guess of the upstream branch.
2539 args = [cl.GetUpstreamBranch()]
2540
maruel@chromium.org13f623c2011-07-22 16:02:23 +00002541 if options.contributor:
2542 if not re.match('^.*\s<\S+@\S+>$', options.contributor):
2543 print "Please provide contibutor as 'First Last <email@example.com>'"
2544 return 1
2545
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002546 base_branch = args[0]
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002547 base_has_submodules = IsSubmoduleMergeCommit(base_branch)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002548
sbc@chromium.org71437c02015-04-09 19:29:40 +00002549 if git_common.is_dirty_git_tree(cmd):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002550 return 1
2551
2552 # This rev-list syntax means "show all commits not in my branch that
2553 # are in base_branch".
2554 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(),
2555 base_branch]).splitlines()
2556 if upstream_commits:
2557 print ('Base branch "%s" has %d commits '
2558 'not in this branch.' % (base_branch, len(upstream_commits)))
2559 print 'Run "git merge %s" before attempting to %s.' % (base_branch, cmd)
2560 return 1
2561
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002562 # This is the revision `svn dcommit` will commit on top of.
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002563 svn_head = None
2564 if cmd == 'dcommit' or base_has_submodules:
2565 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1',
2566 '--pretty=format:%H'])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002567
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002568 if cmd == 'dcommit':
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002569 # If the base_head is a submodule merge commit, the first parent of the
2570 # base_head should be a git-svn commit, which is what we're interested in.
2571 base_svn_head = base_branch
2572 if base_has_submodules:
2573 base_svn_head += '^1'
2574
2575 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002576 if extra_commits:
2577 print ('This branch has %d additional commits not upstreamed yet.'
2578 % len(extra_commits.splitlines()))
2579 print ('Upstream "%s" or rebase this branch on top of the upstream trunk '
2580 'before attempting to %s.' % (base_branch, cmd))
2581 return 1
2582
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002583 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip()
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002584 if not options.bypass_hooks:
maruel@chromium.org13f623c2011-07-22 16:02:23 +00002585 author = None
2586 if options.contributor:
2587 author = re.search(r'\<(.*)\>', options.contributor).group(1)
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002588 hook_results = cl.RunHook(
2589 committing=True,
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002590 may_prompt=not options.force,
2591 verbose=options.verbose,
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002592 change=cl.GetChange(merge_base, author))
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002593 if not hook_results.should_continue():
2594 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002595
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002596 # Check the tree status if the tree status URL is set.
2597 status = GetTreeStatus()
2598 if 'closed' == status:
2599 print('The tree is closed. Please wait for it to reopen. Use '
2600 '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
2601 return 1
2602 elif 'unknown' == status:
2603 print('Unable to determine tree status. Please verify manually and '
2604 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
2605 return 1
maruel@chromium.orgac637152012-01-16 14:19:54 +00002606 else:
2607 breakpad.SendStack(
2608 'GitClHooksBypassedCommit',
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002609 'Issue %s/%s bypassed hook when committing (tree status was "%s")' %
2610 (cl.GetRietveldServer(), cl.GetIssue(), GetTreeStatus()),
maruel@chromium.org2e72bb12012-01-17 15:18:35 +00002611 verbose=False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002612
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002613 change_desc = ChangeDescription(options.message)
2614 if not change_desc.description and cl.GetIssue():
2615 change_desc = ChangeDescription(cl.GetDescription())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002616
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002617 if not change_desc.description:
erg@chromium.org1a173982012-08-29 20:43:05 +00002618 if not cl.GetIssue() and options.bypass_hooks:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002619 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base]))
erg@chromium.org1a173982012-08-29 20:43:05 +00002620 else:
2621 print 'No description set.'
2622 print 'Visit %s/edit to set it.' % (cl.GetIssueURL())
2623 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002624
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002625 # Keep a separate copy for the commit message, because the commit message
2626 # contains the link to the Rietveld issue, while the Rietveld message contains
2627 # the commit viewvc url.
maruel@chromium.orge52678e2013-04-26 18:34:44 +00002628 # Keep a separate copy for the commit message.
2629 if cl.GetIssue():
maruel@chromium.orgcf087782013-07-23 13:08:48 +00002630 change_desc.update_reviewers(cl.GetApprovingReviewers())
maruel@chromium.orge52678e2013-04-26 18:34:44 +00002631
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002632 commit_desc = ChangeDescription(change_desc.description)
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00002633 if cl.GetIssue():
smut@google.com4c61dcc2015-06-08 22:31:29 +00002634 # Xcode won't linkify this URL unless there is a non-whitespace character
sergiyb@chromium.org4b39c5f2015-07-07 10:33:12 +00002635 # after it. Add a period on a new line to circumvent this. Also add a space
2636 # before the period to make sure that Gitiles continues to correctly resolve
2637 # the URL.
2638 commit_desc.append_footer('Review URL: %s .' % cl.GetIssueURL())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002639 if options.contributor:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002640 commit_desc.append_footer('Patch from %s.' % options.contributor)
2641
agable@chromium.orgeec3ea32013-08-15 20:31:39 +00002642 print('Description:')
2643 print(commit_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002644
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002645 branches = [merge_base, cl.GetBranchRef()]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002646 if not options.force:
iannucci@chromium.org79540052012-10-19 23:15:26 +00002647 print_stats(options.similarity, options.find_copies, branches)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002648
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002649 # We want to squash all this branch's commits into one commit with the proper
2650 # description. We do this by doing a "reset --soft" to the base branch (which
2651 # keeps the working copy the same), then dcommitting that. If origin/master
2652 # has a submodule merge commit, we'll also need to cherry-pick the squashed
2653 # commit onto a branch based on the git-svn head.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002654 MERGE_BRANCH = 'git-cl-commit'
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002655 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
2656 # Delete the branches if they exist.
2657 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]:
2658 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch]
2659 result = RunGitWithCode(showref_cmd)
2660 if result[0] == 0:
2661 RunGit(['branch', '-D', branch])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002662
2663 # We might be in a directory that's present in this branch but not in the
2664 # trunk. Move up to the top of the tree so that git commands that expect a
2665 # valid CWD won't fail after we check out the merge branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002666 rel_base_path = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002667 if rel_base_path:
2668 os.chdir(rel_base_path)
2669
2670 # Stuff our change into the merge branch.
2671 # We wrap in a try...finally block so if anything goes wrong,
2672 # we clean up the branches.
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002673 retcode = -1
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002674 pushed_to_pending = False
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002675 pending_ref = None
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002676 revision = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002677 try:
bauerb@chromium.orgb4a75c42011-03-08 08:35:38 +00002678 RunGit(['checkout', '-q', '-b', MERGE_BRANCH])
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002679 RunGit(['reset', '--soft', merge_base])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002680 if options.contributor:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002681 RunGit(
2682 [
2683 'commit', '--author', options.contributor,
2684 '-m', commit_desc.description,
2685 ])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002686 else:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002687 RunGit(['commit', '-m', commit_desc.description])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002688 if base_has_submodules:
2689 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip()
2690 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head])
2691 RunGit(['checkout', CHERRY_PICK_BRANCH])
2692 RunGit(['cherry-pick', cherry_pick_commit])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002693 if cmd == 'land':
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00002694 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002695 pending_prefix = settings.GetPendingRefPrefix()
2696 if not pending_prefix or branch.startswith(pending_prefix):
2697 # If not using refs/pending/heads/* at all, or target ref is already set
2698 # to pending, then push to the target ref directly.
2699 retcode, output = RunGitWithCode(
2700 ['push', '--porcelain', remote, 'HEAD:%s' % branch])
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002701 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002702 else:
2703 # Cherry-pick the change on top of pending ref and then push it.
2704 assert branch.startswith('refs/'), branch
2705 assert pending_prefix[-1] == '/', pending_prefix
2706 pending_ref = pending_prefix + branch[len('refs/'):]
2707 retcode, output = PushToGitPending(remote, pending_ref, branch)
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002708 pushed_to_pending = (retcode == 0)
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002709 if retcode == 0:
2710 revision = RunGit(['rev-parse', 'HEAD']).strip()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002711 else:
2712 # dcommit the merge branch.
iannucci@chromium.orga1950c42014-12-05 22:15:56 +00002713 cmd_args = [
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00002714 'svn', 'dcommit',
2715 '-C%s' % options.similarity,
2716 '--no-rebase', '--rmdir',
2717 ]
2718 if settings.GetForceHttpsCommitUrl():
2719 # Allow forcing https commit URLs for some projects that don't allow
2720 # committing to http URLs (like Google Code).
2721 remote_url = cl.GetGitSvnRemoteUrl()
2722 if urlparse.urlparse(remote_url).scheme == 'http':
2723 remote_url = remote_url.replace('http://', 'https://')
iannucci@chromium.orga1950c42014-12-05 22:15:56 +00002724 cmd_args.append('--commit-url=%s' % remote_url)
2725 _, output = RunGitWithCode(cmd_args)
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002726 if 'Committed r' in output:
2727 revision = re.match(
2728 '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1)
2729 logging.debug(output)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002730 finally:
2731 # And then swap back to the original branch and clean up.
2732 RunGit(['checkout', '-q', cl.GetBranch()])
2733 RunGit(['branch', '-D', MERGE_BRANCH])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002734 if base_has_submodules:
2735 RunGit(['branch', '-D', CHERRY_PICK_BRANCH])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002736
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002737 if not revision:
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002738 print 'Failed to push. If this persists, please file a bug.'
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002739 return 1
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002740
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002741 killed = False
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002742 if pushed_to_pending:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002743 try:
2744 revision = WaitForRealCommit(remote, revision, base_branch, branch)
2745 # We set pushed_to_pending to False, since it made it all the way to the
2746 # real ref.
2747 pushed_to_pending = False
2748 except KeyboardInterrupt:
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002749 killed = True
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002750
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002751 if cl.GetIssue():
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002752 to_pending = ' to pending queue' if pushed_to_pending else ''
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002753 viewvc_url = settings.GetViewVCUrl()
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002754 if not to_pending:
2755 if viewvc_url and revision:
2756 change_desc.append_footer(
2757 'Committed: %s%s' % (viewvc_url, revision))
2758 elif revision:
2759 change_desc.append_footer('Committed: %s' % (revision,))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002760 print ('Closing issue '
2761 '(you may be prompted for your codereview password)...')
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002762 cl.UpdateDescription(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002763 cl.CloseIssue()
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002764 props = cl.GetIssueProperties()
sadrul@chromium.org34b5d822013-02-18 01:39:24 +00002765 patch_num = len(props['patchsets'])
rmistry@google.com52d224a2014-08-27 14:44:41 +00002766 comment = "Committed patchset #%d (id:%d)%s manually as %s" % (
mark@chromium.org782570c2014-09-26 21:48:02 +00002767 patch_num, props['patchsets'][-1], to_pending, revision)
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002768 if options.bypass_hooks:
2769 comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.'
2770 else:
2771 comment += ' (presubmit successful).'
iannucci@chromium.orgb85a3162013-01-26 01:11:13 +00002772 cl.RpcServer().add_comment(cl.GetIssue(), comment)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002773 cl.SetIssue(None)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002774
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002775 if pushed_to_pending:
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002776 _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
2777 print 'The commit is in the pending queue (%s).' % pending_ref
2778 print (
thakis@chromium.org5f32a962014-09-05 21:33:23 +00002779 'It will show up on %s in ~1 min, once it gets a Cr-Commit-Position '
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002780 'footer.' % branch)
2781
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002782 hook = POSTUPSTREAM_HOOK_PATTERN % cmd
2783 if os.path.isfile(hook):
2784 RunCommand([hook, merge_base], error_ok=True)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002785
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002786 return 1 if killed else 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002787
2788
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002789def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
2790 print
2791 print 'Waiting for commit to be landed on %s...' % real_ref
2792 print '(If you are impatient, you may Ctrl-C once without harm)'
2793 target_tree = RunGit(['rev-parse', '%s:' % pushed_commit]).strip()
2794 current_rev = RunGit(['rev-parse', local_base_ref]).strip()
2795
2796 loop = 0
2797 while True:
2798 sys.stdout.write('fetching (%d)... \r' % loop)
2799 sys.stdout.flush()
2800 loop += 1
2801
2802 RunGit(['retry', 'fetch', remote, real_ref], stderr=subprocess2.VOID)
2803 to_rev = RunGit(['rev-parse', 'FETCH_HEAD']).strip()
2804 commits = RunGit(['rev-list', '%s..%s' % (current_rev, to_rev)])
2805 for commit in commits.splitlines():
2806 if RunGit(['rev-parse', '%s:' % commit]).strip() == target_tree:
2807 print 'Found commit on %s' % real_ref
2808 return commit
2809
2810 current_rev = to_rev
2811
2812
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002813def PushToGitPending(remote, pending_ref, upstream_ref):
2814 """Fetches pending_ref, cherry-picks current HEAD on top of it, pushes.
2815
2816 Returns:
2817 (retcode of last operation, output log of last operation).
2818 """
2819 assert pending_ref.startswith('refs/'), pending_ref
2820 local_pending_ref = 'refs/git-cl/' + pending_ref[len('refs/'):]
2821 cherry = RunGit(['rev-parse', 'HEAD']).strip()
2822 code = 0
2823 out = ''
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002824 max_attempts = 3
2825 attempts_left = max_attempts
2826 while attempts_left:
2827 if attempts_left != max_attempts:
2828 print 'Retrying, %d attempts left...' % (attempts_left - 1,)
2829 attempts_left -= 1
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002830
2831 # Fetch. Retry fetch errors.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002832 print 'Fetching pending ref %s...' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002833 code, out = RunGitWithCode(
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002834 ['retry', 'fetch', remote, '+%s:%s' % (pending_ref, local_pending_ref)])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002835 if code:
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002836 print 'Fetch failed with exit code %d.' % code
2837 if out.strip():
2838 print out.strip()
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002839 continue
2840
2841 # Try to cherry pick. Abort on merge conflicts.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002842 print 'Cherry-picking commit on top of pending ref...'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002843 RunGitWithCode(['checkout', local_pending_ref], suppress_stderr=True)
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002844 code, out = RunGitWithCode(['cherry-pick', cherry])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002845 if code:
2846 print (
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002847 'Your patch doesn\'t apply cleanly to ref \'%s\', '
2848 'the following files have merge conflicts:' % pending_ref)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002849 print RunGit(['diff', '--name-status', '--diff-filter=U']).strip()
2850 print 'Please rebase your patch and try again.'
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002851 RunGitWithCode(['cherry-pick', '--abort'])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002852 return code, out
2853
2854 # Applied cleanly, try to push now. Retry on error (flake or non-ff push).
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002855 print 'Pushing commit to %s... It can take a while.' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002856 code, out = RunGitWithCode(
2857 ['retry', 'push', '--porcelain', remote, 'HEAD:%s' % pending_ref])
2858 if code == 0:
2859 # Success.
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002860 print 'Commit pushed to pending ref successfully!'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002861 return code, out
2862
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002863 print 'Push failed with exit code %d.' % code
2864 if out.strip():
2865 print out.strip()
2866 if IsFatalPushFailure(out):
2867 print (
2868 'Fatal push error. Make sure your .netrc credentials and git '
2869 'user.email are correct and you have push access to the repo.')
2870 return code, out
2871
2872 print 'All attempts to push to pending ref failed.'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002873 return code, out
2874
2875
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002876def IsFatalPushFailure(push_stdout):
2877 """True if retrying push won't help."""
2878 return '(prohibited by Gerrit)' in push_stdout
2879
2880
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002881@subcommand.usage('[upstream branch to apply against]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002882def CMDdcommit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002883 """Commits the current changelist via git-svn."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002884 if not settings.GetIsGitSvn():
mmoss@chromium.orgf0e41522015-06-10 19:52:01 +00002885 if get_footer_svn_id():
2886 # If it looks like previous commits were mirrored with git-svn.
2887 message = """This repository appears to be a git-svn mirror, but no
2888upstream SVN master is set. You probably need to run 'git auto-svn' once."""
2889 else:
2890 message = """This doesn't appear to be an SVN repository.
2891If your project has a true, writeable git repository, you probably want to run
2892'git cl land' instead.
2893If your project has a git mirror of an upstream SVN master, you probably need
2894to run 'git svn init'.
2895
2896Using the wrong command might cause your commit to appear to succeed, and the
2897review to be closed, without actually landing upstream. If you choose to
2898proceed, please verify that the commit lands upstream as expected."""
thakis@chromium.orgcde3bb62011-01-20 01:16:14 +00002899 print(message)
maruel@chromium.org90541732011-04-01 17:54:18 +00002900 ask_for_data('[Press enter to dcommit or ctrl-C to quit]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002901 return SendUpstream(parser, args, 'dcommit')
2902
2903
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002904@subcommand.usage('[upstream branch to apply against]')
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00002905def CMDland(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002906 """Commits the current changelist via git."""
mmoss@chromium.orgf0e41522015-06-10 19:52:01 +00002907 if settings.GetIsGitSvn() or get_footer_svn_id():
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002908 print('This appears to be an SVN repository.')
2909 print('Are you sure you didn\'t mean \'git cl dcommit\'?')
mmoss@chromium.orgf0e41522015-06-10 19:52:01 +00002910 print('(Ignore if this is the first commit after migrating from svn->git)')
maruel@chromium.org90541732011-04-01 17:54:18 +00002911 ask_for_data('[Press enter to push or ctrl-C to quit]')
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002912 return SendUpstream(parser, args, 'land')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002913
2914
dsinclair@chromium.orgfbed6562015-09-25 21:22:36 +00002915def ParseIssueNum(arg):
2916 """Parses the issue number from args if present otherwise returns None."""
2917 if re.match(r'\d+', arg):
2918 return arg
2919 if arg.startswith('http'):
2920 return re.sub(r'.*/(\d+)/?', r'\1', arg)
2921 return None
2922
2923
2924@subcommand.usage('<patch url or issue id or issue url>')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002925def CMDpatch(parser, args):
marq@chromium.orge5e59002013-10-02 23:21:25 +00002926 """Patches in a code review."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002927 parser.add_option('-b', dest='newbranch',
2928 help='create a new branch off trunk for the patch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002929 parser.add_option('-f', '--force', action='store_true',
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002930 help='with -b, clobber any existing branch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002931 parser.add_option('-d', '--directory', action='store', metavar='DIR',
2932 help='Change to the directory DIR immediately, '
2933 'before doing anything else.')
2934 parser.add_option('--reject', action='store_true',
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00002935 help='failed patches spew .rej files rather than '
2936 'attempting a 3-way merge')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002937 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit',
2938 help="don't commit after patch applies")
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002939 auth.add_auth_options(parser)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002940 (options, args) = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002941 auth_config = auth.extract_auth_config_from_options(options)
2942
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002943 if len(args) != 1:
2944 parser.print_help()
2945 return 1
dsinclair@chromium.orgfbed6562015-09-25 21:22:36 +00002946
2947 issue_arg = ParseIssueNum(args[0])
2948 # The patch URL works because ParseIssueNum won't do any substitution
2949 # as the re.sub pattern fails to match and just returns it.
2950 if issue_arg == None:
2951 parser.print_help()
2952 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002953
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002954 # We don't want uncommitted changes mixed up with the patch.
sbc@chromium.org71437c02015-04-09 19:29:40 +00002955 if git_common.is_dirty_git_tree('patch'):
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002956 return 1
2957
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002958 # TODO(maruel): Use apply_issue.py
ukai@chromium.orge8077812012-02-03 03:41:46 +00002959 # TODO(ukai): use gerrit-cherry-pick for gerrit repository?
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002960
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002961 if options.newbranch:
2962 if options.force:
2963 RunGit(['branch', '-D', options.newbranch],
2964 stderr=subprocess2.PIPE, error_ok=True)
2965 RunGit(['checkout', '-b', options.newbranch,
2966 Changelist().GetUpstreamBranch()])
2967
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002968 return PatchIssue(issue_arg, options.reject, options.nocommit,
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002969 options.directory, auth_config)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002970
2971
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002972def PatchIssue(issue_arg, reject, nocommit, directory, auth_config):
wychen@chromium.orga872e752015-04-28 23:42:18 +00002973 # PatchIssue should never be called with a dirty tree. It is up to the
2974 # caller to check this, but just in case we assert here since the
2975 # consequences of the caller not checking this could be dire.
sbc@chromium.org71437c02015-04-09 19:29:40 +00002976 assert(not git_common.is_dirty_git_tree('apply'))
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002977
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002978 if type(issue_arg) is int or issue_arg.isdigit():
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002979 # Input is an issue id. Figure out the URL.
maruel@chromium.org52424302012-08-29 15:14:30 +00002980 issue = int(issue_arg)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002981 cl = Changelist(issue=issue, auth_config=auth_config)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002982 patchset = cl.GetMostRecentPatchset()
binji@chromium.org0281f522012-09-14 13:37:59 +00002983 patch_data = cl.GetPatchSetDiff(issue, patchset)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002984 else:
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00002985 # Assume it's a URL to the patch. Default to https.
2986 issue_url = gclient_utils.UpgradeToHttps(issue_arg)
kjellander@chromium.org44424542015-06-02 18:35:29 +00002987 match = re.match(r'(.*?)/download/issue(\d+)_(\d+).diff', issue_url)
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002988 if not match:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002989 DieWithError('Must pass an issue ID or full URL for '
2990 '\'Download raw patch set\'')
kjellander@chromium.org44424542015-06-02 18:35:29 +00002991 issue = int(match.group(2))
2992 cl = Changelist(issue=issue, auth_config=auth_config)
2993 cl.rietveld_server = match.group(1)
2994 patchset = int(match.group(3))
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002995 patch_data = urllib2.urlopen(issue_arg).read()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002996
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002997 # Switch up to the top-level directory, if necessary, in preparation for
2998 # applying the patch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002999 top = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003000 if top:
3001 os.chdir(top)
3002
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003003 # Git patches have a/ at the beginning of source paths. We strip that out
3004 # with a sed script rather than the -p flag to patch so we can feed either
3005 # Git or svn-style patches into the same apply command.
3006 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00003007 try:
3008 patch_data = subprocess2.check_output(
3009 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
3010 except subprocess2.CalledProcessError:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003011 DieWithError('Git patch mungling failed.')
3012 logging.info(patch_data)
bratell@opera.com82b91cd2013-07-09 06:33:41 +00003013
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003014 # We use "git apply" to apply the patch instead of "patch" so that we can
3015 # pick up file adds.
3016 # The --index flag means: also insert into the index (so we catch adds).
bratell@opera.com82b91cd2013-07-09 06:33:41 +00003017 cmd = ['git', 'apply', '--index', '-p0']
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00003018 if directory:
3019 cmd.extend(('--directory', directory))
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003020 if reject:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003021 cmd.append('--reject')
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00003022 elif IsGitVersionAtLeast('1.7.12'):
3023 cmd.append('--3way')
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00003024 try:
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003025 subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
bratell@opera.com82b91cd2013-07-09 06:33:41 +00003026 stdin=patch_data, stdout=subprocess2.VOID)
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00003027 except subprocess2.CalledProcessError:
wychen@chromium.orga872e752015-04-28 23:42:18 +00003028 print 'Failed to apply the patch'
3029 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003030
3031 # If we had an issue, commit the current state and register the issue.
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003032 if not nocommit:
wychen@chromium.org5b3bebb2015-05-28 21:41:43 +00003033 RunGit(['commit', '-m', (cl.GetDescription() + '\n\n' +
3034 'patch from issue %(i)s at patchset '
carlosk@chromium.org71284d92014-11-14 18:12:50 +00003035 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)'
3036 % {'i': issue, 'p': patchset})])
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003037 cl = Changelist(auth_config=auth_config)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003038 cl.SetIssue(issue)
binji@chromium.org0281f522012-09-14 13:37:59 +00003039 cl.SetPatchset(patchset)
pdr@chromium.org98ca6622013-04-09 20:58:40 +00003040 print "Committed patch locally."
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003041 else:
3042 print "Patch applied to index."
3043 return 0
3044
3045
3046def CMDrebase(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003047 """Rebases current branch on top of svn repo."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003048 # Provide a wrapper for git svn rebase to help avoid accidental
3049 # git svn dcommit.
3050 # It's the only command that doesn't use parser at all since we just defer
3051 # execution to git-svn.
bratell@opera.com82b91cd2013-07-09 06:33:41 +00003052
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003053 return RunGitWithCode(['svn', 'rebase'] + args)[1]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003054
3055
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00003056def GetTreeStatus(url=None):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003057 """Fetches the tree status and returns either 'open', 'closed',
3058 'unknown' or 'unset'."""
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00003059 url = url or settings.GetTreeStatusUrl(error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003060 if url:
3061 status = urllib2.urlopen(url).read().lower()
3062 if status.find('closed') != -1 or status == '0':
3063 return 'closed'
3064 elif status.find('open') != -1 or status == '1':
3065 return 'open'
3066 return 'unknown'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003067 return 'unset'
3068
dpranke@chromium.org970c5222011-03-12 00:32:24 +00003069
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003070def GetTreeStatusReason():
3071 """Fetches the tree status from a json url and returns the message
3072 with the reason for the tree to be opened or closed."""
msb@chromium.orgbf1a7ba2011-02-01 16:21:46 +00003073 url = settings.GetTreeStatusUrl()
3074 json_url = urlparse.urljoin(url, '/current?format=json')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003075 connection = urllib2.urlopen(json_url)
3076 status = json.loads(connection.read())
3077 connection.close()
3078 return status['message']
3079
dpranke@chromium.org970c5222011-03-12 00:32:24 +00003080
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00003081def GetBuilderMaster(bot_list):
3082 """For a given builder, fetch the master from AE if available."""
3083 map_url = 'https://builders-map.appspot.com/'
3084 try:
3085 master_map = json.load(urllib2.urlopen(map_url))
3086 except urllib2.URLError as e:
3087 return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' %
3088 (map_url, e))
3089 except ValueError as e:
3090 return None, ('Invalid json string from %s. Error: %s.' % (map_url, e))
3091 if not master_map:
3092 return None, 'Failed to build master map.'
3093
3094 result_master = ''
3095 for bot in bot_list:
3096 builder = bot.split(':', 1)[0]
3097 master_list = master_map.get(builder, [])
3098 if not master_list:
3099 return None, ('No matching master for builder %s.' % builder)
3100 elif len(master_list) > 1:
3101 return None, ('The builder name %s exists in multiple masters %s.' %
3102 (builder, master_list))
3103 else:
3104 cur_master = master_list[0]
3105 if not result_master:
3106 result_master = cur_master
3107 elif result_master != cur_master:
3108 return None, 'The builders do not belong to the same master.'
3109 return result_master, None
3110
3111
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003112def CMDtree(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003113 """Shows the status of the tree."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00003114 _, args = parser.parse_args(args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003115 status = GetTreeStatus()
3116 if 'unset' == status:
3117 print 'You must configure your tree status URL by running "git cl config".'
3118 return 2
3119
3120 print "The tree is %s" % status
3121 print
3122 print GetTreeStatusReason()
3123 if status != 'open':
3124 return 1
3125 return 0
3126
3127
maruel@chromium.org15192402012-09-06 12:38:29 +00003128def CMDtry(parser, args):
sheyang@chromium.orgdb375572015-08-17 19:22:23 +00003129 """Triggers a try job through BuildBucket."""
maruel@chromium.org15192402012-09-06 12:38:29 +00003130 group = optparse.OptionGroup(parser, "Try job options")
3131 group.add_option(
3132 "-b", "--bot", action="append",
3133 help=("IMPORTANT: specify ONE builder per --bot flag. Use it multiple "
3134 "times to specify multiple builders. ex: "
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00003135 "'-b win_rel -b win_layout'. See "
maruel@chromium.org15192402012-09-06 12:38:29 +00003136 "the try server waterfall for the builders name and the tests "
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00003137 "available."))
maruel@chromium.org15192402012-09-06 12:38:29 +00003138 group.add_option(
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003139 "-m", "--master", default='',
iannucci@chromium.org9e849272014-04-04 00:31:55 +00003140 help=("Specify a try master where to run the tries."))
hinoka@chromium.orgfeb9e2a2015-09-25 19:11:09 +00003141 group.add_option( "--luci", action='store_true')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003142 group.add_option(
maruel@chromium.org15192402012-09-06 12:38:29 +00003143 "-r", "--revision",
3144 help="Revision to use for the try job; default: the "
3145 "revision will be determined by the try server; see "
3146 "its waterfall for more info")
3147 group.add_option(
3148 "-c", "--clobber", action="store_true", default=False,
3149 help="Force a clobber before building; e.g. don't do an "
3150 "incremental build")
3151 group.add_option(
3152 "--project",
3153 help="Override which project to use. Projects are defined "
3154 "server-side to define what default bot set to use")
3155 group.add_option(
machenbach@chromium.org45453142015-09-15 08:45:22 +00003156 "-p", "--property", dest="properties", action="append", default=[],
3157 help="Specify generic properties in the form -p key1=value1 -p "
3158 "key2=value2 etc (buildbucket only). The value will be treated as "
3159 "json if decodable, or as string otherwise.")
3160 group.add_option(
maruel@chromium.org15192402012-09-06 12:38:29 +00003161 "-n", "--name", help="Try job name; default to current branch name")
sheyang@google.com6ebaf782015-05-12 19:17:54 +00003162 group.add_option(
sheyang@chromium.orgdb375572015-08-17 19:22:23 +00003163 "--use-rietveld", action="store_true", default=False,
3164 help="Use Rietveld to trigger try jobs.")
3165 group.add_option(
3166 "--buildbucket-host", default='cr-buildbucket.appspot.com',
3167 help="Host of buildbucket. The default host is %default.")
maruel@chromium.org15192402012-09-06 12:38:29 +00003168 parser.add_option_group(group)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003169 auth.add_auth_options(parser)
maruel@chromium.org15192402012-09-06 12:38:29 +00003170 options, args = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003171 auth_config = auth.extract_auth_config_from_options(options)
maruel@chromium.org15192402012-09-06 12:38:29 +00003172
machenbach@chromium.org45453142015-09-15 08:45:22 +00003173 if options.use_rietveld and options.properties:
3174 parser.error('Properties can only be specified with buildbucket')
3175
3176 # Make sure that all properties are prop=value pairs.
3177 bad_params = [x for x in options.properties if '=' not in x]
3178 if bad_params:
3179 parser.error('Got properties with missing "=": %s' % bad_params)
3180
maruel@chromium.org15192402012-09-06 12:38:29 +00003181 if args:
3182 parser.error('Unknown arguments: %s' % args)
3183
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003184 cl = Changelist(auth_config=auth_config)
maruel@chromium.org15192402012-09-06 12:38:29 +00003185 if not cl.GetIssue():
3186 parser.error('Need to upload first')
3187
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00003188 props = cl.GetIssueProperties()
agable@chromium.org787e3062014-08-20 16:31:19 +00003189 if props.get('closed'):
3190 parser.error('Cannot send tryjobs for a closed CL')
3191
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00003192 if props.get('private'):
3193 parser.error('Cannot use trybots with private issue')
3194
maruel@chromium.org15192402012-09-06 12:38:29 +00003195 if not options.name:
3196 options.name = cl.GetBranch()
3197
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00003198 if options.bot and not options.master:
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00003199 options.master, err_msg = GetBuilderMaster(options.bot)
3200 if err_msg:
3201 parser.error('Tryserver master cannot be found because: %s\n'
3202 'Please manually specify the tryserver master'
3203 ', e.g. "-m tryserver.chromium.linux".' % err_msg)
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00003204
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003205 def GetMasterMap():
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00003206 # Process --bot.
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003207 if not options.bot:
3208 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
maruel@chromium.org15192402012-09-06 12:38:29 +00003209
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003210 # Get try masters from PRESUBMIT.py files.
3211 masters = presubmit_support.DoGetTryMasters(
3212 change,
3213 change.LocalPaths(),
3214 settings.GetRoot(),
3215 None,
3216 None,
3217 options.verbose,
3218 sys.stdout)
3219 if masters:
3220 return masters
stip@chromium.org43064fd2013-12-18 20:07:44 +00003221
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003222 # Fall back to deprecated method: get try slaves from PRESUBMIT.py files.
3223 options.bot = presubmit_support.DoGetTrySlaves(
3224 change,
3225 change.LocalPaths(),
3226 settings.GetRoot(),
3227 None,
3228 None,
3229 options.verbose,
3230 sys.stdout)
tandrii@chromium.org71184c02016-01-13 15:18:44 +00003231
3232 if not options.bot:
3233 # Get try masters from cq.cfg if any.
3234 # TODO(tandrii): some (but very few) projects store cq.cfg in different
3235 # location.
3236 cq_cfg = os.path.join(change.RepositoryRoot(),
3237 'infra', 'config', 'cq.cfg')
3238 if os.path.exists(cq_cfg):
3239 masters = {}
3240 cq_masters = commit_queue.get_master_builder_map(cq_cfg)
3241 for master, builders in cq_masters.iteritems():
3242 for builder in builders:
3243 # Skip presubmit builders, because these will fail without LGTM.
3244 if 'presubmit' not in builder.lower():
3245 masters.setdefault(master, {})[builder] = ['defaulttests']
3246 if masters:
3247 return masters
3248
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003249 if not options.bot:
3250 parser.error('No default try builder to try, use --bot')
maruel@chromium.org15192402012-09-06 12:38:29 +00003251
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003252 builders_and_tests = {}
3253 # TODO(machenbach): The old style command-line options don't support
3254 # multiple try masters yet.
3255 old_style = filter(lambda x: isinstance(x, basestring), options.bot)
3256 new_style = filter(lambda x: isinstance(x, tuple), options.bot)
3257
3258 for bot in old_style:
3259 if ':' in bot:
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00003260 parser.error('Specifying testfilter is no longer supported')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003261 elif ',' in bot:
3262 parser.error('Specify one bot per --bot flag')
3263 else:
tandrii@chromium.org3764fa22015-10-21 16:40:40 +00003264 builders_and_tests.setdefault(bot, [])
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003265
3266 for bot, tests in new_style:
3267 builders_and_tests.setdefault(bot, []).extend(tests)
3268
3269 # Return a master map with one master to be backwards compatible. The
3270 # master name defaults to an empty string, which will cause the master
3271 # not to be set on rietveld (deprecated).
3272 return {options.master: builders_and_tests}
3273
3274 masters = GetMasterMap()
stip@chromium.org43064fd2013-12-18 20:07:44 +00003275
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003276 for builders in masters.itervalues():
3277 if any('triggered' in b for b in builders):
3278 print >> sys.stderr, (
3279 'ERROR You are trying to send a job to a triggered bot. This type of'
3280 ' bot requires an\ninitial job from a parent (usually a builder). '
3281 'Instead send your job to the parent.\n'
3282 'Bot list: %s' % builders)
3283 return 1
ilevy@chromium.orgf3b21232012-09-24 20:48:55 +00003284
ilevy@chromium.org36e420b2013-08-06 23:21:12 +00003285 patchset = cl.GetMostRecentPatchset()
3286 if patchset and patchset != cl.GetPatchset():
3287 print(
3288 '\nWARNING Mismatch between local config and server. Did a previous '
3289 'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
3290 'Continuing using\npatchset %s.\n' % patchset)
hinoka@chromium.orgfeb9e2a2015-09-25 19:11:09 +00003291 if options.luci:
3292 trigger_luci_job(cl, masters, options)
3293 elif not options.use_rietveld:
sheyang@google.com6ebaf782015-05-12 19:17:54 +00003294 try:
3295 trigger_try_jobs(auth_config, cl, options, masters, 'git_cl_try')
3296 except BuildbucketResponseException as ex:
3297 print 'ERROR: %s' % ex
fischman@chromium.orgd246c972013-12-21 22:47:38 +00003298 return 1
sheyang@google.com6ebaf782015-05-12 19:17:54 +00003299 except Exception as e:
3300 stacktrace = (''.join(traceback.format_stack()) + traceback.format_exc())
3301 print 'ERROR: Exception when trying to trigger tryjobs: %s\n%s' % (
3302 e, stacktrace)
3303 return 1
3304 else:
3305 try:
3306 cl.RpcServer().trigger_distributed_try_jobs(
3307 cl.GetIssue(), patchset, options.name, options.clobber,
3308 options.revision, masters)
3309 except urllib2.HTTPError as e:
3310 if e.code == 404:
3311 print('404 from rietveld; '
3312 'did you mean to use "git try" instead of "git cl try"?')
3313 return 1
3314 print('Tried jobs on:')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003315
sheyang@google.com6ebaf782015-05-12 19:17:54 +00003316 for (master, builders) in sorted(masters.iteritems()):
3317 if master:
3318 print 'Master: %s' % master
3319 length = max(len(builder) for builder in builders)
3320 for builder in sorted(builders):
3321 print ' %*s: %s' % (length, builder, ','.join(builders[builder]))
maruel@chromium.org15192402012-09-06 12:38:29 +00003322 return 0
3323
3324
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003325@subcommand.usage('[new upstream branch]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003326def CMDupstream(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003327 """Prints or sets the name of the upstream branch, if any."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00003328 _, args = parser.parse_args(args)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00003329 if len(args) > 1:
maruel@chromium.org27bb3872011-05-30 20:33:19 +00003330 parser.error('Unrecognized args: %s' % ' '.join(args))
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00003331
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003332 cl = Changelist()
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00003333 if args:
3334 # One arg means set upstream branch.
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00003335 branch = cl.GetBranch()
3336 RunGit(['branch', '--set-upstream', branch, args[0]])
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00003337 cl = Changelist()
3338 print "Upstream branch set to " + cl.GetUpstreamBranch()
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00003339
3340 # Clear configured merge-base, if there is one.
3341 git_common.remove_merge_base(branch)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00003342 else:
3343 print cl.GetUpstreamBranch()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003344 return 0
3345
3346
thestig@chromium.org00858c82013-12-02 23:08:03 +00003347def CMDweb(parser, args):
3348 """Opens the current CL in the web browser."""
3349 _, args = parser.parse_args(args)
3350 if args:
3351 parser.error('Unrecognized args: %s' % ' '.join(args))
3352
3353 issue_url = Changelist().GetIssueURL()
3354 if not issue_url:
3355 print >> sys.stderr, 'ERROR No issue to open'
3356 return 1
3357
3358 webbrowser.open(issue_url)
3359 return 0
3360
3361
maruel@chromium.org27bb3872011-05-30 20:33:19 +00003362def CMDset_commit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003363 """Sets the commit bit to trigger the Commit Queue."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003364 auth.add_auth_options(parser)
3365 options, args = parser.parse_args(args)
3366 auth_config = auth.extract_auth_config_from_options(options)
maruel@chromium.org27bb3872011-05-30 20:33:19 +00003367 if args:
3368 parser.error('Unrecognized args: %s' % ' '.join(args))
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003369 cl = Changelist(auth_config=auth_config)
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00003370 props = cl.GetIssueProperties()
3371 if props.get('private'):
3372 parser.error('Cannot set commit on private issue')
maruel@chromium.org27bb3872011-05-30 20:33:19 +00003373 cl.SetFlag('commit', '1')
3374 return 0
3375
3376
groby@chromium.org411034a2013-02-26 15:12:01 +00003377def CMDset_close(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003378 """Closes the issue."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003379 auth.add_auth_options(parser)
3380 options, args = parser.parse_args(args)
3381 auth_config = auth.extract_auth_config_from_options(options)
groby@chromium.org411034a2013-02-26 15:12:01 +00003382 if args:
3383 parser.error('Unrecognized args: %s' % ' '.join(args))
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003384 cl = Changelist(auth_config=auth_config)
groby@chromium.org411034a2013-02-26 15:12:01 +00003385 # Ensure there actually is an issue to close.
3386 cl.GetDescription()
3387 cl.CloseIssue()
3388 return 0
3389
3390
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003391def CMDdiff(parser, args):
wychen@chromium.org37b2ec02015-04-03 00:49:15 +00003392 """Shows differences between local tree and last upload."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003393 auth.add_auth_options(parser)
3394 options, args = parser.parse_args(args)
3395 auth_config = auth.extract_auth_config_from_options(options)
3396 if args:
3397 parser.error('Unrecognized args: %s' % ' '.join(args))
wychen@chromium.org46309bf2015-04-03 21:04:49 +00003398
3399 # Uncommitted (staged and unstaged) changes will be destroyed by
3400 # "git reset --hard" if there are merging conflicts in PatchIssue().
3401 # Staged changes would be committed along with the patch from last
3402 # upload, hence counted toward the "last upload" side in the final
3403 # diff output, and this is not what we want.
sbc@chromium.org71437c02015-04-09 19:29:40 +00003404 if git_common.is_dirty_git_tree('diff'):
wychen@chromium.org46309bf2015-04-03 21:04:49 +00003405 return 1
3406
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003407 cl = Changelist(auth_config=auth_config)
sbc@chromium.org78dc9842013-11-25 18:43:44 +00003408 issue = cl.GetIssue()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003409 branch = cl.GetBranch()
sbc@chromium.org78dc9842013-11-25 18:43:44 +00003410 if not issue:
3411 DieWithError('No issue found for current branch (%s)' % branch)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003412 TMP_BRANCH = 'git-cl-diff'
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003413 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003414
3415 # Create a new branch based on the merge-base
3416 RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch])
3417 try:
3418 # Patch in the latest changes from rietveld.
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003419 rtn = PatchIssue(issue, False, False, None, auth_config)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003420 if rtn != 0:
wychen@chromium.orga872e752015-04-28 23:42:18 +00003421 RunGit(['reset', '--hard'])
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003422 return rtn
3423
wychen@chromium.org06928532015-02-03 02:11:29 +00003424 # Switch back to starting branch and diff against the temporary
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003425 # branch containing the latest rietveld patch.
wychen@chromium.org06928532015-02-03 02:11:29 +00003426 subprocess2.check_call(['git', 'diff', TMP_BRANCH, branch, '--'])
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003427 finally:
3428 RunGit(['checkout', '-q', branch])
3429 RunGit(['branch', '-D', TMP_BRANCH])
3430
3431 return 0
3432
3433
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003434def CMDowners(parser, args):
wychen@chromium.org37b2ec02015-04-03 00:49:15 +00003435 """Interactively find the owners for reviewing."""
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003436 parser.add_option(
3437 '--no-color',
3438 action='store_true',
3439 help='Use this option to disable color output')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003440 auth.add_auth_options(parser)
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003441 options, args = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003442 auth_config = auth.extract_auth_config_from_options(options)
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003443
3444 author = RunGit(['config', 'user.email']).strip() or None
3445
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003446 cl = Changelist(auth_config=auth_config)
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003447
3448 if args:
3449 if len(args) > 1:
3450 parser.error('Unknown args')
3451 base_branch = args[0]
3452 else:
3453 # Default to diffing against the common ancestor of the upstream branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003454 base_branch = cl.GetCommonAncestorWithUpstream()
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003455
3456 change = cl.GetChange(base_branch, None)
3457 return owners_finder.OwnersFinder(
3458 [f.LocalPath() for f in
3459 cl.GetChange(base_branch, None).AffectedFiles()],
3460 change.RepositoryRoot(), author,
3461 fopen=file, os_path=os.path, glob=glob.glob,
3462 disable_color=options.no_color).run()
3463
3464
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003465def BuildGitDiffCmd(diff_type, upstream_commit, args, extensions):
3466 """Generates a diff command."""
3467 # Generate diff for the current branch's changes.
3468 diff_cmd = ['diff', '--no-ext-diff', '--no-prefix', diff_type,
3469 upstream_commit, '--' ]
3470
3471 if args:
3472 for arg in args:
3473 if os.path.isdir(arg):
3474 diff_cmd.extend(os.path.join(arg, '*' + ext) for ext in extensions)
3475 elif os.path.isfile(arg):
3476 diff_cmd.append(arg)
3477 else:
3478 DieWithError('Argument "%s" is not a file or a directory' % arg)
3479 else:
3480 diff_cmd.extend('*' + ext for ext in extensions)
3481
3482 return diff_cmd
3483
3484
enne@chromium.org555cfe42014-01-29 18:21:39 +00003485@subcommand.usage('[files or directories to diff]')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003486def CMDformat(parser, args):
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003487 """Runs auto-formatting tools (clang-format etc.) on the diff."""
thakis@chromium.org9819b1b2014-12-09 21:21:53 +00003488 CLANG_EXTS = ['.cc', '.cpp', '.h', '.mm', '.proto', '.java']
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003489 parser.add_option('--full', action='store_true',
3490 help='Reformat the full content of all touched files')
3491 parser.add_option('--dry-run', action='store_true',
3492 help='Don\'t modify any file on disk.')
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003493 parser.add_option('--python', action='store_true',
3494 help='Format python code with yapf (experimental).')
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003495 parser.add_option('--diff', action='store_true',
3496 help='Print diff to stdout rather than modifying files.')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003497 opts, args = parser.parse_args(args)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003498
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00003499 # git diff generates paths against the root of the repository. Change
3500 # to that directory so clang-format can find files even within subdirs.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003501 rel_base_path = settings.GetRelativeRoot()
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00003502 if rel_base_path:
3503 os.chdir(rel_base_path)
3504
digit@chromium.org29e47272013-05-17 17:01:46 +00003505 # Grab the merge-base commit, i.e. the upstream commit of the current
3506 # branch when it was created or the last time it was rebased. This is
3507 # to cover the case where the user may have called "git fetch origin",
3508 # moving the origin branch to a newer commit, but hasn't rebased yet.
3509 upstream_commit = None
3510 cl = Changelist()
3511 upstream_branch = cl.GetUpstreamBranch()
3512 if upstream_branch:
3513 upstream_commit = RunGit(['merge-base', 'HEAD', upstream_branch])
3514 upstream_commit = upstream_commit.strip()
3515
3516 if not upstream_commit:
3517 DieWithError('Could not find base commit for this branch. '
3518 'Are you in detached state?')
3519
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003520 if opts.full:
3521 # Only list the names of modified files.
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003522 diff_type = '--name-only'
enne@chromium.org555cfe42014-01-29 18:21:39 +00003523 else:
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003524 # Only generate context-less patches.
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003525 diff_type = '-U0'
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003526
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003527 diff_cmd = BuildGitDiffCmd(diff_type, upstream_commit, args, CLANG_EXTS)
digit@chromium.org29e47272013-05-17 17:01:46 +00003528 diff_output = RunGit(diff_cmd)
3529
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003530 top_dir = os.path.normpath(
3531 RunGit(["rev-parse", "--show-toplevel"]).rstrip('\n'))
3532
3533 # Locate the clang-format binary in the checkout
3534 try:
3535 clang_format_tool = clang_format.FindClangFormatToolInChromiumTree()
3536 except clang_format.NotFoundError, e:
3537 DieWithError(e)
mdempsky@google.comc3b3dc02013-08-05 23:09:49 +00003538
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003539 # Set to 2 to signal to CheckPatchFormatted() that this patch isn't
3540 # formatted. This is used to block during the presubmit.
3541 return_value = 0
3542
digit@chromium.org29e47272013-05-17 17:01:46 +00003543 if opts.full:
3544 # diff_output is a list of files to send to clang-format.
3545 files = diff_output.splitlines()
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003546 if files:
3547 cmd = [clang_format_tool]
3548 if not opts.dry_run and not opts.diff:
3549 cmd.append('-i')
3550 stdout = RunCommand(cmd + files, cwd=top_dir)
3551 if opts.diff:
3552 sys.stdout.write(stdout)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003553 else:
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003554 env = os.environ.copy()
thakis@chromium.orgbd2a9b92014-09-05 18:54:22 +00003555 env['PATH'] = str(os.path.dirname(clang_format_tool))
digit@chromium.org29e47272013-05-17 17:01:46 +00003556 # diff_output is a patch to send to clang-format-diff.py
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003557 try:
3558 script = clang_format.FindClangFormatScriptInChromiumTree(
3559 'clang-format-diff.py')
3560 except clang_format.NotFoundError, e:
3561 DieWithError(e)
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00003562
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003563 cmd = [sys.executable, script, '-p0']
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003564 if not opts.dry_run and not opts.diff:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003565 cmd.append('-i')
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00003566
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003567 stdout = RunCommand(cmd, stdin=diff_output, cwd=top_dir, env=env)
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003568 if opts.diff:
3569 sys.stdout.write(stdout)
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003570 if opts.dry_run and len(stdout) > 0:
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003571 return_value = 2
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003572
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003573 # Similar code to above, but using yapf on .py files rather than clang-format
3574 # on C/C++ files
3575 if opts.python:
3576 diff_cmd = BuildGitDiffCmd(diff_type, upstream_commit, args, ['.py'])
3577 diff_output = RunGit(diff_cmd)
3578 yapf_tool = gclient_utils.FindExecutable('yapf')
3579 if yapf_tool is None:
3580 DieWithError('yapf not found in PATH')
3581
3582 if opts.full:
3583 files = diff_output.splitlines()
3584 if files:
3585 cmd = [yapf_tool]
3586 if not opts.dry_run and not opts.diff:
3587 cmd.append('-i')
3588 stdout = RunCommand(cmd + files, cwd=top_dir)
3589 if opts.diff:
3590 sys.stdout.write(stdout)
3591 else:
3592 # TODO(sbc): yapf --lines mode still has some issues.
3593 # https://github.com/google/yapf/issues/154
3594 DieWithError('--python currently only works with --full')
3595
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003596 # Build a diff command that only operates on dart files. dart's formatter
3597 # does not have the nice property of only operating on modified chunks, so
3598 # hard code full.
3599 dart_diff_cmd = BuildGitDiffCmd('--name-only', upstream_commit,
3600 args, ['.dart'])
3601 dart_diff_output = RunGit(dart_diff_cmd)
3602 if dart_diff_output:
3603 try:
3604 command = [dart_format.FindDartFmtToolInChromiumTree()]
3605 if not opts.dry_run and not opts.diff:
3606 command.append('-w')
3607 command.extend(dart_diff_output.splitlines())
3608
3609 stdout = RunCommand(command, cwd=top_dir, env=env)
3610 if opts.dry_run and stdout:
3611 return_value = 2
3612 except dart_format.NotFoundError as e:
erikcorry@chromium.org3e445022015-12-17 09:07:26 +00003613 print ('Warning: Unable to check Dart code formatting. Dart SDK not ' +
3614 'found in this checkout. Files in other languages are still ' +
3615 'formatted.')
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003616
3617 return return_value
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003618
3619
scottmg@chromium.org84a80c42015-09-22 20:40:37 +00003620@subcommand.usage('<codereview url or issue id>')
3621def CMDcheckout(parser, args):
3622 """Checks out a branch associated with a given Rietveld issue."""
3623 _, args = parser.parse_args(args)
3624
3625 if len(args) != 1:
3626 parser.print_help()
3627 return 1
3628
dsinclair@chromium.orgfbed6562015-09-25 21:22:36 +00003629 target_issue = ParseIssueNum(args[0])
3630 if target_issue == None:
scottmg@chromium.org84a80c42015-09-22 20:40:37 +00003631 parser.print_help()
3632 return 1
3633
3634 key_and_issues = [x.split() for x in RunGit(
3635 ['config', '--local', '--get-regexp', r'branch\..*\.rietveldissue'])
3636 .splitlines()]
3637 branches = []
3638 for key, issue in key_and_issues:
3639 if issue == target_issue:
3640 branches.append(re.sub(r'branch\.(.*)\.rietveldissue', r'\1', key))
3641
3642 if len(branches) == 0:
3643 print 'No branch found for issue %s.' % target_issue
3644 return 1
3645 if len(branches) == 1:
3646 RunGit(['checkout', branches[0]])
3647 else:
3648 print 'Multiple branches match issue %s:' % target_issue
3649 for i in range(len(branches)):
3650 print '%d: %s' % (i, branches[i])
3651 which = raw_input('Choose by index: ')
3652 try:
3653 RunGit(['checkout', branches[int(which)]])
3654 except (IndexError, ValueError):
3655 print 'Invalid selection, not checking out any branch.'
3656 return 1
3657
3658 return 0
3659
3660
maruel@chromium.org29404b52014-09-08 22:58:00 +00003661def CMDlol(parser, args):
3662 # This command is intentionally undocumented.
thakis@chromium.org3421c992014-11-02 02:20:32 +00003663 print zlib.decompress(base64.b64decode(
3664 'eNptkLEOwyAMRHe+wupCIqW57v0Vq84WqWtXyrcXnCBsmgMJ+/SSAxMZgRB6NzE'
3665 'E2ObgCKJooYdu4uAQVffUEoE1sRQLxAcqzd7uK2gmStrll1ucV3uZyaY5sXyDd9'
3666 'JAnN+lAXsOMJ90GANAi43mq5/VeeacylKVgi8o6F1SC63FxnagHfJUTfUYdCR/W'
3667 'Ofe+0dHL7PicpytKP750Fh1q2qnLVof4w8OZWNY'))
maruel@chromium.org29404b52014-09-08 22:58:00 +00003668 return 0
3669
3670
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003671class OptionParser(optparse.OptionParser):
3672 """Creates the option parse and add --verbose support."""
3673 def __init__(self, *args, **kwargs):
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003674 optparse.OptionParser.__init__(
3675 self, *args, prog='git cl', version=__version__, **kwargs)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003676 self.add_option(
3677 '-v', '--verbose', action='count', default=0,
3678 help='Use 2 times for more debugging info')
3679
3680 def parse_args(self, args=None, values=None):
3681 options, args = optparse.OptionParser.parse_args(self, args, values)
3682 levels = [logging.WARNING, logging.INFO, logging.DEBUG]
3683 logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)])
3684 return options, args
3685
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003686
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003687def main(argv):
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003688 if sys.hexversion < 0x02060000:
3689 print >> sys.stderr, (
3690 '\nYour python version %s is unsupported, please upgrade.\n' %
3691 sys.version.split(' ', 1)[0])
3692 return 2
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003693
maruel@chromium.orgddd59412011-11-30 14:20:38 +00003694 # Reload settings.
3695 global settings
3696 settings = Settings()
3697
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003698 colorize_CMDstatus_doc()
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003699 dispatcher = subcommand.CommandDispatcher(__name__)
3700 try:
3701 return dispatcher.execute(OptionParser(), argv)
vadimsh@chromium.orgeed4df32015-04-10 21:30:20 +00003702 except auth.AuthenticationError as e:
3703 DieWithError(str(e))
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003704 except urllib2.HTTPError, e:
3705 if e.code != 500:
3706 raise
3707 DieWithError(
3708 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
3709 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
sbc@chromium.org013731e2015-02-26 18:28:43 +00003710 return 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003711
3712
3713if __name__ == '__main__':
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003714 # These affect sys.stdout so do it outside of main() to simplify mocks in
3715 # unit testing.
maruel@chromium.org6f09cd92011-04-01 16:38:12 +00003716 fix_encoding.fix_encoding()
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003717 colorama.init()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003718 try:
3719 sys.exit(main(sys.argv[1:]))
3720 except KeyboardInterrupt:
3721 sys.stderr.write('interrupted\n')
3722 sys.exit(1)