blob: 2d915b0b7f3a67db26a06344e23a4ba25c8320c8 [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')
tandrii@chromium.orga83663a2016-01-14 16:01:00 +00002032 logging.debug("%s %s", file_handle, msg_file)
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002033 try:
2034 try:
tandrii@chromium.orga83663a2016-01-14 16:01:00 +00002035 try:
2036 fileobj = os.fdopen(file_handle, 'w')
2037 except OSError:
2038 # if fdopen fails, file_handle remains open.
2039 # See https://docs.python.org/2/library/os.html#os.fdopen.
2040 os.close(file_handle)
2041 raise
2042 with fileobj:
2043 # This will close the file_handle.
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002044 fileobj.write(change_desc.description)
tandrii@chromium.orga83663a2016-01-14 16:01:00 +00002045 logging.debug("%s %s finish editing", file_handle, msg_file)
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002046 finally:
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002047 RunCommand([commit_msg_hook, msg_file])
2048 change_desc.set_description(gclient_utils.FileRead(msg_file))
2049 finally:
2050 os.remove(msg_file)
2051
2052 if not change_desc.description:
2053 print "Description is empty; aborting."
2054 return 1
2055
2056 message = change_desc.description
2057
2058 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
2059 if remote is '.':
2060 # If our upstream branch is local, we base our squashed commit on its
2061 # squashed version.
2062 parent = ('refs/heads/git_cl_uploads/' +
2063 scm.GIT.ShortBranchName(upstream_branch))
2064
2065 # Verify that the upstream branch has been uploaded too, otherwise Gerrit
2066 # will create additional CLs when uploading.
2067 if (RunGitSilent(['rev-parse', upstream_branch + ':']) !=
2068 RunGitSilent(['rev-parse', parent + ':'])):
2069 print 'Upload upstream branch ' + upstream_branch + ' first.'
2070 return 1
2071 else:
2072 parent = cl.GetCommonAncestorWithUpstream()
2073
2074 tree = RunGit(['rev-parse', 'HEAD:']).strip()
2075 ref_to_push = RunGit(['commit-tree', tree, '-p', parent,
2076 '-m', message]).strip()
2077 else:
2078 if CHANGE_ID not in change_desc.description:
2079 AddChangeIdToCommitMessage(options, args)
2080 ref_to_push = 'HEAD'
2081 parent = '%s/%s' % (gerrit_remote, branch)
2082
2083 commits = RunGitSilent(['rev-list', '%s..%s' % (parent,
2084 ref_to_push)]).splitlines()
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00002085 if len(commits) > 1:
2086 print('WARNING: This will upload %d commits. Run the following command '
2087 'to see which commits will be uploaded: ' % len(commits))
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002088 print('git log %s..%s' % (parent, ref_to_push))
2089 print('You can also use `git squash-branch` to squash these into a single '
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00002090 'commit.')
2091 ask_for_data('About to upload; enter to confirm.')
2092
piman@chromium.org336f9122014-09-04 02:16:55 +00002093 if options.reviewers or options.tbr_owners:
2094 change_desc.update_reviewers(options.reviewers, options.tbr_owners, change)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002095
ukai@chromium.orge8077812012-02-03 03:41:46 +00002096 receive_options = []
2097 cc = cl.GetCCList().split(',')
2098 if options.cc:
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002099 cc.extend(options.cc)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002100 cc = filter(None, cc)
2101 if cc:
2102 receive_options += ['--cc=' + email for email in cc]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002103 if change_desc.get_reviewers():
2104 receive_options.extend(
2105 '--reviewer=' + email for email in change_desc.get_reviewers())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002106
ukai@chromium.orge8077812012-02-03 03:41:46 +00002107 git_command = ['push']
2108 if receive_options:
ukai@chromium.org19bbfa22012-02-03 16:18:11 +00002109 git_command.append('--receive-pack=git receive-pack %s' %
ukai@chromium.orge8077812012-02-03 03:41:46 +00002110 ' '.join(receive_options))
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002111 git_command += [gerrit_remote, ref_to_push + ':refs/for/' + branch]
ukai@chromium.orge8077812012-02-03 03:41:46 +00002112 RunGit(git_command)
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002113
2114 if options.squash:
2115 head = RunGit(['rev-parse', 'HEAD']).strip()
2116 RunGit(['update-ref', '-m', 'Uploaded ' + head, shadow_branch, ref_to_push])
2117
ukai@chromium.orge8077812012-02-03 03:41:46 +00002118 # TODO(ukai): parse Change-Id: and set issue number?
2119 return 0
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002120
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002121
wittman@chromium.org455dc922015-01-26 20:15:50 +00002122def GetTargetRef(remote, remote_branch, target_branch, pending_prefix):
2123 """Computes the remote branch ref to use for the CL.
2124
2125 Args:
2126 remote (str): The git remote for the CL.
2127 remote_branch (str): The git remote branch for the CL.
2128 target_branch (str): The target branch specified by the user.
2129 pending_prefix (str): The pending prefix from the settings.
2130 """
2131 if not (remote and remote_branch):
2132 return None
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002133
wittman@chromium.org455dc922015-01-26 20:15:50 +00002134 if target_branch:
2135 # Cannonicalize branch references to the equivalent local full symbolic
2136 # refs, which are then translated into the remote full symbolic refs
2137 # below.
2138 if '/' not in target_branch:
2139 remote_branch = 'refs/remotes/%s/%s' % (remote, target_branch)
2140 else:
2141 prefix_replacements = (
2142 ('^((refs/)?remotes/)?branch-heads/', 'refs/remotes/branch-heads/'),
2143 ('^((refs/)?remotes/)?%s/' % remote, 'refs/remotes/%s/' % remote),
2144 ('^(refs/)?heads/', 'refs/remotes/%s/' % remote),
2145 )
2146 match = None
2147 for regex, replacement in prefix_replacements:
2148 match = re.search(regex, target_branch)
2149 if match:
2150 remote_branch = target_branch.replace(match.group(0), replacement)
2151 break
2152 if not match:
2153 # This is a branch path but not one we recognize; use as-is.
2154 remote_branch = target_branch
rmistry@google.comc68112d2015-03-03 12:48:06 +00002155 elif remote_branch in REFS_THAT_ALIAS_TO_OTHER_REFS:
2156 # Handle the refs that need to land in different refs.
2157 remote_branch = REFS_THAT_ALIAS_TO_OTHER_REFS[remote_branch]
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002158
wittman@chromium.org455dc922015-01-26 20:15:50 +00002159 # Create the true path to the remote branch.
2160 # Does the following translation:
2161 # * refs/remotes/origin/refs/diff/test -> refs/diff/test
2162 # * refs/remotes/origin/master -> refs/heads/master
2163 # * refs/remotes/branch-heads/test -> refs/branch-heads/test
2164 if remote_branch.startswith('refs/remotes/%s/refs/' % remote):
2165 remote_branch = remote_branch.replace('refs/remotes/%s/' % remote, '')
2166 elif remote_branch.startswith('refs/remotes/%s/' % remote):
2167 remote_branch = remote_branch.replace('refs/remotes/%s/' % remote,
2168 'refs/heads/')
2169 elif remote_branch.startswith('refs/remotes/branch-heads'):
2170 remote_branch = remote_branch.replace('refs/remotes/', 'refs/')
2171 # If a pending prefix exists then replace refs/ with it.
2172 if pending_prefix:
2173 remote_branch = remote_branch.replace('refs/', pending_prefix)
2174 return remote_branch
2175
2176
piman@chromium.org336f9122014-09-04 02:16:55 +00002177def RietveldUpload(options, args, cl, change):
ukai@chromium.orge8077812012-02-03 03:41:46 +00002178 """upload the patch to rietveld."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002179 upload_args = ['--assume_yes'] # Don't ask about untracked files.
2180 upload_args.extend(['--server', cl.GetRietveldServer()])
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002181 upload_args.extend(auth.auth_config_to_command_options(cl.auth_config))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002182 if options.emulate_svn_auto_props:
2183 upload_args.append('--emulate_svn_auto_props')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002184
2185 change_desc = None
2186
pgervais@chromium.org91141372014-01-09 23:27:20 +00002187 if options.email is not None:
2188 upload_args.extend(['--email', options.email])
2189
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002190 if cl.GetIssue():
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00002191 if options.title:
2192 upload_args.extend(['--title', options.title])
rogerta@chromium.orgafadfca2013-05-29 14:15:53 +00002193 if options.message:
2194 upload_args.extend(['--message', options.message])
maruel@chromium.org52424302012-08-29 15:14:30 +00002195 upload_args.extend(['--issue', str(cl.GetIssue())])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002196 print ("This branch is associated with issue %s. "
2197 "Adding patch to that issue." % cl.GetIssue())
2198 else:
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00002199 if options.title:
2200 upload_args.extend(['--title', options.title])
rogerta@chromium.org43e34f02013-03-25 14:52:48 +00002201 message = options.title or options.message or CreateDescriptionFromLog(args)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002202 change_desc = ChangeDescription(message)
piman@chromium.org336f9122014-09-04 02:16:55 +00002203 if options.reviewers or options.tbr_owners:
2204 change_desc.update_reviewers(options.reviewers,
2205 options.tbr_owners,
2206 change)
maruel@chromium.org71e12a92012-02-14 02:34:15 +00002207 if not options.force:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002208 change_desc.prompt()
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002209
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002210 if not change_desc.description:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002211 print "Description is empty; aborting."
2212 return 1
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002213
maruel@chromium.org71e12a92012-02-14 02:34:15 +00002214 upload_args.extend(['--message', change_desc.description])
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002215 if change_desc.get_reviewers():
2216 upload_args.append('--reviewers=' + ','.join(change_desc.get_reviewers()))
maruel@chromium.orga3353652011-11-30 14:26:57 +00002217 if options.send_mail:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002218 if not change_desc.get_reviewers():
maruel@chromium.orga3353652011-11-30 14:26:57 +00002219 DieWithError("Must specify reviewers to send email.")
2220 upload_args.append('--send_mail')
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +00002221
2222 # We check this before applying rietveld.private assuming that in
2223 # rietveld.cc only addresses which we can send private CLs to are listed
2224 # if rietveld.private is set, and so we should ignore rietveld.cc only when
2225 # --private is specified explicitly on the command line.
2226 if options.private:
2227 logging.warn('rietveld.cc is ignored since private flag is specified. '
2228 'You need to review and add them manually if necessary.')
2229 cc = cl.GetCCListWithoutDefault()
2230 else:
2231 cc = cl.GetCCList()
2232 cc = ','.join(filter(None, (cc, ','.join(options.cc))))
maruel@chromium.orgb2a7c332011-02-25 20:30:37 +00002233 if cc:
2234 upload_args.extend(['--cc', cc])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002235
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00002236 if options.private or settings.GetDefaultPrivateFlag() == "True":
2237 upload_args.append('--private')
2238
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002239 upload_args.extend(['--git_similarity', str(options.similarity)])
iannucci@chromium.org79540052012-10-19 23:15:26 +00002240 if not options.find_copies:
2241 upload_args.extend(['--git_no_find_copies'])
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002242
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002243 # Include the upstream repo's URL in the change -- this is useful for
2244 # projects that have their source spread across multiple repos.
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00002245 remote_url = cl.GetGitBaseUrlFromConfig()
2246 if not remote_url:
2247 if settings.GetIsGitSvn():
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00002248 remote_url = cl.GetGitSvnRemoteUrl()
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00002249 else:
jam@chromium.org80c51ae2014-10-17 18:43:02 +00002250 if cl.GetRemoteUrl() and '/' in cl.GetUpstreamBranch():
2251 remote_url = (cl.GetRemoteUrl() + '@'
2252 + cl.GetUpstreamBranch().split('/')[-1])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002253 if remote_url:
2254 upload_args.extend(['--base_url', remote_url])
rmistry@google.comd1e37582014-12-10 20:58:24 +00002255 remote, remote_branch = cl.GetRemoteBranch()
wittman@chromium.org455dc922015-01-26 20:15:50 +00002256 target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
2257 settings.GetPendingRefPrefix())
2258 if target_ref:
2259 upload_args.extend(['--target_ref', target_ref])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002260
rmistry@google.comd91b7e32015-06-23 11:24:07 +00002261 # Look for dependent patchsets. See crbug.com/480453 for more details.
2262 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
2263 upstream_branch = ShortBranchName(upstream_branch)
2264 if remote is '.':
2265 # A local branch is being tracked.
2266 local_branch = ShortBranchName(upstream_branch)
rmistry@google.com78948ed2015-07-08 23:09:57 +00002267 if settings.GetIsSkipDependencyUpload(local_branch):
rmistry@google.comd91b7e32015-06-23 11:24:07 +00002268 print
rmistry@google.com78948ed2015-07-08 23:09:57 +00002269 print ('Skipping dependency patchset upload because git config '
2270 'branch.%s.skip-deps-uploads is set to True.' % local_branch)
rmistry@google.comd91b7e32015-06-23 11:24:07 +00002271 print
rmistry@google.com78948ed2015-07-08 23:09:57 +00002272 else:
2273 auth_config = auth.extract_auth_config_from_options(options)
2274 branch_cl = Changelist(branchref=local_branch, auth_config=auth_config)
2275 branch_cl_issue_url = branch_cl.GetIssueURL()
2276 branch_cl_issue = branch_cl.GetIssue()
2277 branch_cl_patchset = branch_cl.GetPatchset()
2278 if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset:
2279 upload_args.extend(
2280 ['--depends_on_patchset', '%s:%s' % (
2281 branch_cl_issue, branch_cl_patchset)])
2282 print
2283 print ('The current branch (%s) is tracking a local branch (%s) with '
2284 'an associated CL.') % (cl.GetBranch(), local_branch)
2285 print 'Adding %s/#ps%s as a dependency patchset.' % (
2286 branch_cl_issue_url, branch_cl_patchset)
2287 print
rmistry@google.comd91b7e32015-06-23 11:24:07 +00002288
sheyang@chromium.org152cf832014-06-11 21:37:49 +00002289 project = settings.GetProject()
2290 if project:
2291 upload_args.extend(['--project', project])
2292
rmistry@google.comef966222015-04-07 11:15:01 +00002293 if options.cq_dry_run:
2294 upload_args.extend(['--cq_dry_run'])
2295
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002296 try:
ilevy@chromium.org82880192012-11-26 15:41:57 +00002297 upload_args = ['upload'] + upload_args + args
2298 logging.info('upload.RealMain(%s)', upload_args)
2299 issue, patchset = upload.RealMain(upload_args)
maruel@chromium.org911fce12013-07-29 23:01:13 +00002300 issue = int(issue)
2301 patchset = int(patchset)
maruel@chromium.org9ce0dff2011-04-04 17:56:50 +00002302 except KeyboardInterrupt:
2303 sys.exit(1)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002304 except:
2305 # If we got an exception after the user typed a description for their
2306 # change, back up the description before re-raising.
2307 if change_desc:
2308 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
2309 print '\nGot exception while uploading -- saving description to %s\n' \
2310 % backup_path
2311 backup_file = open(backup_path, 'w')
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002312 backup_file.write(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002313 backup_file.close()
2314 raise
2315
2316 if not cl.GetIssue():
2317 cl.SetIssue(issue)
2318 cl.SetPatchset(patchset)
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002319
2320 if options.use_commit_queue:
2321 cl.SetFlag('commit', '1')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002322 return 0
2323
2324
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002325def cleanup_list(l):
2326 """Fixes a list so that comma separated items are put as individual items.
2327
2328 So that "--reviewers joe@c,john@c --reviewers joa@c" results in
2329 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']).
2330 """
2331 items = sum((i.split(',') for i in l), [])
2332 stripped_items = (i.strip() for i in items)
2333 return sorted(filter(None, stripped_items))
2334
2335
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002336@subcommand.usage('[args to "git diff"]')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002337def CMDupload(parser, args):
rmistry@google.com78948ed2015-07-08 23:09:57 +00002338 """Uploads the current changelist to codereview.
2339
2340 Can skip dependency patchset uploads for a branch by running:
2341 git config branch.branch_name.skip-deps-uploads True
2342 To unset run:
2343 git config --unset branch.branch_name.skip-deps-uploads
2344 Can also set the above globally by using the --global flag.
2345 """
ukai@chromium.orge8077812012-02-03 03:41:46 +00002346 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
2347 help='bypass upload presubmit hook')
brettw@chromium.orgb65c43c2013-06-10 22:04:49 +00002348 parser.add_option('--bypass-watchlists', action='store_true',
2349 dest='bypass_watchlists',
2350 help='bypass watchlists auto CC-ing reviewers')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002351 parser.add_option('-f', action='store_true', dest='force',
2352 help="force yes to questions (don't prompt)")
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00002353 parser.add_option('-m', dest='message', help='message for patchset')
2354 parser.add_option('-t', dest='title', help='title for patchset')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002355 parser.add_option('-r', '--reviewers',
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002356 action='append', default=[],
ukai@chromium.orge8077812012-02-03 03:41:46 +00002357 help='reviewer email addresses')
2358 parser.add_option('--cc',
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002359 action='append', default=[],
ukai@chromium.orge8077812012-02-03 03:41:46 +00002360 help='cc email addresses')
adamk@chromium.org36f47302013-04-05 01:08:31 +00002361 parser.add_option('-s', '--send-mail', action='store_true',
ukai@chromium.orge8077812012-02-03 03:41:46 +00002362 help='send email to reviewer immediately')
pgervais@chromium.orgb9f27512014-08-08 15:52:33 +00002363 parser.add_option('--emulate_svn_auto_props',
2364 '--emulate-svn-auto-props',
2365 action="store_true",
ukai@chromium.orge8077812012-02-03 03:41:46 +00002366 dest="emulate_svn_auto_props",
2367 help="Emulate Subversion's auto properties feature.")
ukai@chromium.orge8077812012-02-03 03:41:46 +00002368 parser.add_option('-c', '--use-commit-queue', action='store_true',
2369 help='tell the commit queue to commit this patchset')
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00002370 parser.add_option('--private', action='store_true',
2371 help='set the review private (rietveld only)')
ukai@chromium.org8ef7ab22012-11-28 04:24:52 +00002372 parser.add_option('--target_branch',
pgervais@chromium.orgb9f27512014-08-08 15:52:33 +00002373 '--target-branch',
wittman@chromium.org455dc922015-01-26 20:15:50 +00002374 metavar='TARGET',
2375 help='Apply CL to remote ref TARGET. ' +
2376 'Default: remote branch head, or master')
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002377 parser.add_option('--squash', action='store_true',
2378 help='Squash multiple commits into one (Gerrit only)')
bauerb@chromium.org54b400c2016-01-14 10:08:25 +00002379 parser.add_option('--no-squash', action='store_true',
2380 help='Don\'t squash multiple commits into one ' +
2381 '(Gerrit only)')
pgervais@chromium.org91141372014-01-09 23:27:20 +00002382 parser.add_option('--email', default=None,
2383 help='email address to use to connect to Rietveld')
piman@chromium.org336f9122014-09-04 02:16:55 +00002384 parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true',
2385 help='add a set of OWNERS to TBR')
tandrii@chromium.orgd50452a2015-11-23 16:38:15 +00002386 parser.add_option('-d', '--cq-dry-run', dest='cq_dry_run',
2387 action='store_true',
rmistry@google.comef966222015-04-07 11:15:01 +00002388 help='Send the patchset to do a CQ dry run right after '
2389 'upload.')
rmistry@google.com2dd99862015-06-22 12:22:18 +00002390 parser.add_option('--dependencies', action='store_true',
2391 help='Uploads CLs of all the local branches that depend on '
2392 'the current branch')
pgervais@chromium.org91141372014-01-09 23:27:20 +00002393
rmistry@google.com2dd99862015-06-22 12:22:18 +00002394 orig_args = args
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002395 add_git_similarity(parser)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002396 auth.add_auth_options(parser)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002397 (options, args) = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002398 auth_config = auth.extract_auth_config_from_options(options)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002399
sbc@chromium.org71437c02015-04-09 19:29:40 +00002400 if git_common.is_dirty_git_tree('upload'):
ukai@chromium.orge8077812012-02-03 03:41:46 +00002401 return 1
2402
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002403 options.reviewers = cleanup_list(options.reviewers)
2404 options.cc = cleanup_list(options.cc)
2405
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002406 cl = Changelist(auth_config=auth_config)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002407 if args:
2408 # TODO(ukai): is it ok for gerrit case?
2409 base_branch = args[0]
2410 else:
luqui@chromium.org64e14362015-01-07 00:29:29 +00002411 if cl.GetBranch() is None:
2412 DieWithError('Can\'t upload from detached HEAD state. Get on a branch!')
2413
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00002414 # Default to diffing against common ancestor of upstream branch
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002415 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org5e07e062013-02-28 23:55:44 +00002416 args = [base_branch, 'HEAD']
ukai@chromium.orge8077812012-02-03 03:41:46 +00002417
vadimsh@chromium.orgeed4df32015-04-10 21:30:20 +00002418 # Make sure authenticated to Rietveld before running expensive hooks. It is
2419 # a fast, best efforts check. Rietveld still can reject the authentication
2420 # during the actual upload.
2421 if not settings.GetIsGerrit() and auth_config.use_oauth2:
2422 authenticator = auth.get_authenticator_for_host(
2423 cl.GetRietveldServer(), auth_config)
2424 if not authenticator.has_cached_credentials():
2425 raise auth.LoginRequiredError(cl.GetRietveldServer())
2426
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002427 # Apply watchlists on upload.
2428 change = cl.GetChange(base_branch, None)
2429 watchlist = watchlists.Watchlists(change.RepositoryRoot())
2430 files = [f.LocalPath() for f in change.AffectedFiles()]
brettw@chromium.orgb65c43c2013-06-10 22:04:49 +00002431 if not options.bypass_watchlists:
2432 cl.SetWatchers(watchlist.GetWatchersForPaths(files))
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002433
ukai@chromium.orge8077812012-02-03 03:41:46 +00002434 if not options.bypass_hooks:
piman@chromium.org336f9122014-09-04 02:16:55 +00002435 if options.reviewers or options.tbr_owners:
isherman@chromium.orgb5cded62014-03-25 17:47:57 +00002436 # Set the reviewer list now so that presubmit checks can access it.
2437 change_description = ChangeDescription(change.FullDescriptionText())
piman@chromium.org336f9122014-09-04 02:16:55 +00002438 change_description.update_reviewers(options.reviewers,
2439 options.tbr_owners,
2440 change)
isherman@chromium.orgb5cded62014-03-25 17:47:57 +00002441 change.SetDescriptionText(change_description.description)
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002442 hook_results = cl.RunHook(committing=False,
ukai@chromium.orge8077812012-02-03 03:41:46 +00002443 may_prompt=not options.force,
2444 verbose=options.verbose,
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002445 change=change)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002446 if not hook_results.should_continue():
2447 return 1
2448 if not options.reviewers and hook_results.reviewers:
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002449 options.reviewers = hook_results.reviewers.split(',')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002450
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002451 if cl.GetIssue():
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002452 latest_patchset = cl.GetMostRecentPatchset()
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002453 local_patchset = cl.GetPatchset()
dmikurube@chromium.org07d149f2013-04-03 11:40:23 +00002454 if latest_patchset and local_patchset and local_patchset != latest_patchset:
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002455 print ('The last upload made from this repository was patchset #%d but '
2456 'the most recent patchset on the server is #%d.'
2457 % (local_patchset, latest_patchset))
koz@chromium.orgc7192782013-04-09 23:28:46 +00002458 print ('Uploading will still work, but if you\'ve uploaded to this issue '
2459 'from another machine or branch the patch you\'re uploading now '
2460 'might not include those changes.')
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002461 ask_for_data('About to upload; enter to confirm.')
2462
iannucci@chromium.org79540052012-10-19 23:15:26 +00002463 print_stats(options.similarity, options.find_copies, args)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002464 if settings.GetIsGerrit():
bauerb@chromium.org54b400c2016-01-14 10:08:25 +00002465 if options.squash and options.no_squash:
2466 DieWithError('Can only use one of --squash or --no-squash')
2467
2468 options.squash = ((settings.GetSquashGerritUploads() or options.squash) and
2469 not options.no_squash)
2470
piman@chromium.org336f9122014-09-04 02:16:55 +00002471 return GerritUpload(options, args, cl, change)
2472 ret = RietveldUpload(options, args, cl, change)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00002473 if not ret:
rogerta@chromium.org4a6cd042013-04-12 15:40:42 +00002474 git_set_branch_value('last-upload-hash',
2475 RunGit(['rev-parse', 'HEAD']).strip())
rmistry@google.com5626a922015-02-26 14:03:30 +00002476 # Run post upload hooks, if specified.
2477 if settings.GetRunPostUploadHook():
2478 presubmit_support.DoPostUploadExecuter(
2479 change,
2480 cl,
2481 settings.GetRoot(),
2482 options.verbose,
2483 sys.stdout)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00002484
rmistry@google.com2dd99862015-06-22 12:22:18 +00002485 # Upload all dependencies if specified.
2486 if options.dependencies:
2487 print
2488 print '--dependencies has been specified.'
2489 print 'All dependent local branches will be re-uploaded.'
2490 print
2491 # Remove the dependencies flag from args so that we do not end up in a
2492 # loop.
2493 orig_args.remove('--dependencies')
2494 upload_branch_deps(cl, orig_args)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00002495 return ret
ukai@chromium.orge8077812012-02-03 03:41:46 +00002496
2497
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002498def IsSubmoduleMergeCommit(ref):
2499 # When submodules are added to the repo, we expect there to be a single
2500 # non-git-svn merge commit at remote HEAD with a signature comment.
2501 pattern = '^SVN changes up to revision [0-9]*$'
szager@chromium.orge84b7542012-06-15 21:26:58 +00002502 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref]
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002503 return RunGit(cmd) != ''
2504
2505
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002506def SendUpstream(parser, args, cmd):
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00002507 """Common code for CMDland and CmdDCommit
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002508
iannucci@chromium.org5724c962014-04-11 09:32:56 +00002509 Squashes branch into a single commit.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002510 Updates changelog with metadata (e.g. pointer to review).
2511 Pushes/dcommits the code upstream.
2512 Updates review and closes.
2513 """
2514 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
2515 help='bypass upload presubmit hook')
2516 parser.add_option('-m', dest='message',
2517 help="override review description")
2518 parser.add_option('-f', action='store_true', dest='force',
2519 help="force yes to questions (don't prompt)")
2520 parser.add_option('-c', dest='contributor',
2521 help="external contributor for patch (appended to " +
2522 "description and used as author for git). Should be " +
2523 "formatted as 'First Last <email@example.com>'")
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002524 add_git_similarity(parser)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002525 auth.add_auth_options(parser)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002526 (options, args) = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002527 auth_config = auth.extract_auth_config_from_options(options)
2528
2529 cl = Changelist(auth_config=auth_config)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002530
iannucci@chromium.org5724c962014-04-11 09:32:56 +00002531 current = cl.GetBranch()
2532 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
2533 if not settings.GetIsGitSvn() and remote == '.':
2534 print
2535 print 'Attempting to push branch %r into another local branch!' % current
2536 print
2537 print 'Either reparent this branch on top of origin/master:'
2538 print ' git reparent-branch --root'
2539 print
2540 print 'OR run `git rebase-update` if you think the parent branch is already'
2541 print 'committed.'
2542 print
2543 print ' Current parent: %r' % upstream_branch
2544 return 1
2545
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002546 if not args or cmd == 'land':
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002547 # Default to merging against our best guess of the upstream branch.
2548 args = [cl.GetUpstreamBranch()]
2549
maruel@chromium.org13f623c2011-07-22 16:02:23 +00002550 if options.contributor:
2551 if not re.match('^.*\s<\S+@\S+>$', options.contributor):
2552 print "Please provide contibutor as 'First Last <email@example.com>'"
2553 return 1
2554
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002555 base_branch = args[0]
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002556 base_has_submodules = IsSubmoduleMergeCommit(base_branch)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002557
sbc@chromium.org71437c02015-04-09 19:29:40 +00002558 if git_common.is_dirty_git_tree(cmd):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002559 return 1
2560
2561 # This rev-list syntax means "show all commits not in my branch that
2562 # are in base_branch".
2563 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(),
2564 base_branch]).splitlines()
2565 if upstream_commits:
2566 print ('Base branch "%s" has %d commits '
2567 'not in this branch.' % (base_branch, len(upstream_commits)))
2568 print 'Run "git merge %s" before attempting to %s.' % (base_branch, cmd)
2569 return 1
2570
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002571 # This is the revision `svn dcommit` will commit on top of.
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002572 svn_head = None
2573 if cmd == 'dcommit' or base_has_submodules:
2574 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1',
2575 '--pretty=format:%H'])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002576
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002577 if cmd == 'dcommit':
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002578 # If the base_head is a submodule merge commit, the first parent of the
2579 # base_head should be a git-svn commit, which is what we're interested in.
2580 base_svn_head = base_branch
2581 if base_has_submodules:
2582 base_svn_head += '^1'
2583
2584 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002585 if extra_commits:
2586 print ('This branch has %d additional commits not upstreamed yet.'
2587 % len(extra_commits.splitlines()))
2588 print ('Upstream "%s" or rebase this branch on top of the upstream trunk '
2589 'before attempting to %s.' % (base_branch, cmd))
2590 return 1
2591
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002592 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip()
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002593 if not options.bypass_hooks:
maruel@chromium.org13f623c2011-07-22 16:02:23 +00002594 author = None
2595 if options.contributor:
2596 author = re.search(r'\<(.*)\>', options.contributor).group(1)
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002597 hook_results = cl.RunHook(
2598 committing=True,
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002599 may_prompt=not options.force,
2600 verbose=options.verbose,
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002601 change=cl.GetChange(merge_base, author))
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002602 if not hook_results.should_continue():
2603 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002604
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002605 # Check the tree status if the tree status URL is set.
2606 status = GetTreeStatus()
2607 if 'closed' == status:
2608 print('The tree is closed. Please wait for it to reopen. Use '
2609 '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
2610 return 1
2611 elif 'unknown' == status:
2612 print('Unable to determine tree status. Please verify manually and '
2613 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
2614 return 1
maruel@chromium.orgac637152012-01-16 14:19:54 +00002615 else:
2616 breakpad.SendStack(
2617 'GitClHooksBypassedCommit',
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002618 'Issue %s/%s bypassed hook when committing (tree status was "%s")' %
2619 (cl.GetRietveldServer(), cl.GetIssue(), GetTreeStatus()),
maruel@chromium.org2e72bb12012-01-17 15:18:35 +00002620 verbose=False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002621
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002622 change_desc = ChangeDescription(options.message)
2623 if not change_desc.description and cl.GetIssue():
2624 change_desc = ChangeDescription(cl.GetDescription())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002625
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002626 if not change_desc.description:
erg@chromium.org1a173982012-08-29 20:43:05 +00002627 if not cl.GetIssue() and options.bypass_hooks:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002628 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base]))
erg@chromium.org1a173982012-08-29 20:43:05 +00002629 else:
2630 print 'No description set.'
2631 print 'Visit %s/edit to set it.' % (cl.GetIssueURL())
2632 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002633
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002634 # Keep a separate copy for the commit message, because the commit message
2635 # contains the link to the Rietveld issue, while the Rietveld message contains
2636 # the commit viewvc url.
maruel@chromium.orge52678e2013-04-26 18:34:44 +00002637 # Keep a separate copy for the commit message.
2638 if cl.GetIssue():
maruel@chromium.orgcf087782013-07-23 13:08:48 +00002639 change_desc.update_reviewers(cl.GetApprovingReviewers())
maruel@chromium.orge52678e2013-04-26 18:34:44 +00002640
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002641 commit_desc = ChangeDescription(change_desc.description)
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00002642 if cl.GetIssue():
smut@google.com4c61dcc2015-06-08 22:31:29 +00002643 # Xcode won't linkify this URL unless there is a non-whitespace character
sergiyb@chromium.org4b39c5f2015-07-07 10:33:12 +00002644 # after it. Add a period on a new line to circumvent this. Also add a space
2645 # before the period to make sure that Gitiles continues to correctly resolve
2646 # the URL.
2647 commit_desc.append_footer('Review URL: %s .' % cl.GetIssueURL())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002648 if options.contributor:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002649 commit_desc.append_footer('Patch from %s.' % options.contributor)
2650
agable@chromium.orgeec3ea32013-08-15 20:31:39 +00002651 print('Description:')
2652 print(commit_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002653
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002654 branches = [merge_base, cl.GetBranchRef()]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002655 if not options.force:
iannucci@chromium.org79540052012-10-19 23:15:26 +00002656 print_stats(options.similarity, options.find_copies, branches)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002657
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002658 # We want to squash all this branch's commits into one commit with the proper
2659 # description. We do this by doing a "reset --soft" to the base branch (which
2660 # keeps the working copy the same), then dcommitting that. If origin/master
2661 # has a submodule merge commit, we'll also need to cherry-pick the squashed
2662 # commit onto a branch based on the git-svn head.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002663 MERGE_BRANCH = 'git-cl-commit'
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002664 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
2665 # Delete the branches if they exist.
2666 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]:
2667 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch]
2668 result = RunGitWithCode(showref_cmd)
2669 if result[0] == 0:
2670 RunGit(['branch', '-D', branch])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002671
2672 # We might be in a directory that's present in this branch but not in the
2673 # trunk. Move up to the top of the tree so that git commands that expect a
2674 # valid CWD won't fail after we check out the merge branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002675 rel_base_path = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002676 if rel_base_path:
2677 os.chdir(rel_base_path)
2678
2679 # Stuff our change into the merge branch.
2680 # We wrap in a try...finally block so if anything goes wrong,
2681 # we clean up the branches.
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002682 retcode = -1
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002683 pushed_to_pending = False
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002684 pending_ref = None
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002685 revision = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002686 try:
bauerb@chromium.orgb4a75c42011-03-08 08:35:38 +00002687 RunGit(['checkout', '-q', '-b', MERGE_BRANCH])
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002688 RunGit(['reset', '--soft', merge_base])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002689 if options.contributor:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002690 RunGit(
2691 [
2692 'commit', '--author', options.contributor,
2693 '-m', commit_desc.description,
2694 ])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002695 else:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002696 RunGit(['commit', '-m', commit_desc.description])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002697 if base_has_submodules:
2698 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip()
2699 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head])
2700 RunGit(['checkout', CHERRY_PICK_BRANCH])
2701 RunGit(['cherry-pick', cherry_pick_commit])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002702 if cmd == 'land':
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00002703 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002704 pending_prefix = settings.GetPendingRefPrefix()
2705 if not pending_prefix or branch.startswith(pending_prefix):
2706 # If not using refs/pending/heads/* at all, or target ref is already set
2707 # to pending, then push to the target ref directly.
2708 retcode, output = RunGitWithCode(
2709 ['push', '--porcelain', remote, 'HEAD:%s' % branch])
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002710 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002711 else:
2712 # Cherry-pick the change on top of pending ref and then push it.
2713 assert branch.startswith('refs/'), branch
2714 assert pending_prefix[-1] == '/', pending_prefix
2715 pending_ref = pending_prefix + branch[len('refs/'):]
2716 retcode, output = PushToGitPending(remote, pending_ref, branch)
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002717 pushed_to_pending = (retcode == 0)
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002718 if retcode == 0:
2719 revision = RunGit(['rev-parse', 'HEAD']).strip()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002720 else:
2721 # dcommit the merge branch.
iannucci@chromium.orga1950c42014-12-05 22:15:56 +00002722 cmd_args = [
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00002723 'svn', 'dcommit',
2724 '-C%s' % options.similarity,
2725 '--no-rebase', '--rmdir',
2726 ]
2727 if settings.GetForceHttpsCommitUrl():
2728 # Allow forcing https commit URLs for some projects that don't allow
2729 # committing to http URLs (like Google Code).
2730 remote_url = cl.GetGitSvnRemoteUrl()
2731 if urlparse.urlparse(remote_url).scheme == 'http':
2732 remote_url = remote_url.replace('http://', 'https://')
iannucci@chromium.orga1950c42014-12-05 22:15:56 +00002733 cmd_args.append('--commit-url=%s' % remote_url)
2734 _, output = RunGitWithCode(cmd_args)
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002735 if 'Committed r' in output:
2736 revision = re.match(
2737 '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1)
2738 logging.debug(output)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002739 finally:
2740 # And then swap back to the original branch and clean up.
2741 RunGit(['checkout', '-q', cl.GetBranch()])
2742 RunGit(['branch', '-D', MERGE_BRANCH])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002743 if base_has_submodules:
2744 RunGit(['branch', '-D', CHERRY_PICK_BRANCH])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002745
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002746 if not revision:
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002747 print 'Failed to push. If this persists, please file a bug.'
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002748 return 1
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002749
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002750 killed = False
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002751 if pushed_to_pending:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002752 try:
2753 revision = WaitForRealCommit(remote, revision, base_branch, branch)
2754 # We set pushed_to_pending to False, since it made it all the way to the
2755 # real ref.
2756 pushed_to_pending = False
2757 except KeyboardInterrupt:
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002758 killed = True
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002759
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002760 if cl.GetIssue():
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002761 to_pending = ' to pending queue' if pushed_to_pending else ''
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002762 viewvc_url = settings.GetViewVCUrl()
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002763 if not to_pending:
2764 if viewvc_url and revision:
2765 change_desc.append_footer(
2766 'Committed: %s%s' % (viewvc_url, revision))
2767 elif revision:
2768 change_desc.append_footer('Committed: %s' % (revision,))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002769 print ('Closing issue '
2770 '(you may be prompted for your codereview password)...')
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002771 cl.UpdateDescription(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002772 cl.CloseIssue()
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002773 props = cl.GetIssueProperties()
sadrul@chromium.org34b5d822013-02-18 01:39:24 +00002774 patch_num = len(props['patchsets'])
rmistry@google.com52d224a2014-08-27 14:44:41 +00002775 comment = "Committed patchset #%d (id:%d)%s manually as %s" % (
mark@chromium.org782570c2014-09-26 21:48:02 +00002776 patch_num, props['patchsets'][-1], to_pending, revision)
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002777 if options.bypass_hooks:
2778 comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.'
2779 else:
2780 comment += ' (presubmit successful).'
iannucci@chromium.orgb85a3162013-01-26 01:11:13 +00002781 cl.RpcServer().add_comment(cl.GetIssue(), comment)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002782 cl.SetIssue(None)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002783
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002784 if pushed_to_pending:
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002785 _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
2786 print 'The commit is in the pending queue (%s).' % pending_ref
2787 print (
thakis@chromium.org5f32a962014-09-05 21:33:23 +00002788 'It will show up on %s in ~1 min, once it gets a Cr-Commit-Position '
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002789 'footer.' % branch)
2790
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002791 hook = POSTUPSTREAM_HOOK_PATTERN % cmd
2792 if os.path.isfile(hook):
2793 RunCommand([hook, merge_base], error_ok=True)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002794
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002795 return 1 if killed else 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002796
2797
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002798def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
2799 print
2800 print 'Waiting for commit to be landed on %s...' % real_ref
2801 print '(If you are impatient, you may Ctrl-C once without harm)'
2802 target_tree = RunGit(['rev-parse', '%s:' % pushed_commit]).strip()
2803 current_rev = RunGit(['rev-parse', local_base_ref]).strip()
2804
2805 loop = 0
2806 while True:
2807 sys.stdout.write('fetching (%d)... \r' % loop)
2808 sys.stdout.flush()
2809 loop += 1
2810
2811 RunGit(['retry', 'fetch', remote, real_ref], stderr=subprocess2.VOID)
2812 to_rev = RunGit(['rev-parse', 'FETCH_HEAD']).strip()
2813 commits = RunGit(['rev-list', '%s..%s' % (current_rev, to_rev)])
2814 for commit in commits.splitlines():
2815 if RunGit(['rev-parse', '%s:' % commit]).strip() == target_tree:
2816 print 'Found commit on %s' % real_ref
2817 return commit
2818
2819 current_rev = to_rev
2820
2821
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002822def PushToGitPending(remote, pending_ref, upstream_ref):
2823 """Fetches pending_ref, cherry-picks current HEAD on top of it, pushes.
2824
2825 Returns:
2826 (retcode of last operation, output log of last operation).
2827 """
2828 assert pending_ref.startswith('refs/'), pending_ref
2829 local_pending_ref = 'refs/git-cl/' + pending_ref[len('refs/'):]
2830 cherry = RunGit(['rev-parse', 'HEAD']).strip()
2831 code = 0
2832 out = ''
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002833 max_attempts = 3
2834 attempts_left = max_attempts
2835 while attempts_left:
2836 if attempts_left != max_attempts:
2837 print 'Retrying, %d attempts left...' % (attempts_left - 1,)
2838 attempts_left -= 1
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002839
2840 # Fetch. Retry fetch errors.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002841 print 'Fetching pending ref %s...' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002842 code, out = RunGitWithCode(
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002843 ['retry', 'fetch', remote, '+%s:%s' % (pending_ref, local_pending_ref)])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002844 if code:
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002845 print 'Fetch failed with exit code %d.' % code
2846 if out.strip():
2847 print out.strip()
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002848 continue
2849
2850 # Try to cherry pick. Abort on merge conflicts.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002851 print 'Cherry-picking commit on top of pending ref...'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002852 RunGitWithCode(['checkout', local_pending_ref], suppress_stderr=True)
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002853 code, out = RunGitWithCode(['cherry-pick', cherry])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002854 if code:
2855 print (
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002856 'Your patch doesn\'t apply cleanly to ref \'%s\', '
2857 'the following files have merge conflicts:' % pending_ref)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002858 print RunGit(['diff', '--name-status', '--diff-filter=U']).strip()
2859 print 'Please rebase your patch and try again.'
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002860 RunGitWithCode(['cherry-pick', '--abort'])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002861 return code, out
2862
2863 # Applied cleanly, try to push now. Retry on error (flake or non-ff push).
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002864 print 'Pushing commit to %s... It can take a while.' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002865 code, out = RunGitWithCode(
2866 ['retry', 'push', '--porcelain', remote, 'HEAD:%s' % pending_ref])
2867 if code == 0:
2868 # Success.
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002869 print 'Commit pushed to pending ref successfully!'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002870 return code, out
2871
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002872 print 'Push failed with exit code %d.' % code
2873 if out.strip():
2874 print out.strip()
2875 if IsFatalPushFailure(out):
2876 print (
2877 'Fatal push error. Make sure your .netrc credentials and git '
2878 'user.email are correct and you have push access to the repo.')
2879 return code, out
2880
2881 print 'All attempts to push to pending ref failed.'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002882 return code, out
2883
2884
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002885def IsFatalPushFailure(push_stdout):
2886 """True if retrying push won't help."""
2887 return '(prohibited by Gerrit)' in push_stdout
2888
2889
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002890@subcommand.usage('[upstream branch to apply against]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002891def CMDdcommit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002892 """Commits the current changelist via git-svn."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002893 if not settings.GetIsGitSvn():
mmoss@chromium.orgf0e41522015-06-10 19:52:01 +00002894 if get_footer_svn_id():
2895 # If it looks like previous commits were mirrored with git-svn.
2896 message = """This repository appears to be a git-svn mirror, but no
2897upstream SVN master is set. You probably need to run 'git auto-svn' once."""
2898 else:
2899 message = """This doesn't appear to be an SVN repository.
2900If your project has a true, writeable git repository, you probably want to run
2901'git cl land' instead.
2902If your project has a git mirror of an upstream SVN master, you probably need
2903to run 'git svn init'.
2904
2905Using the wrong command might cause your commit to appear to succeed, and the
2906review to be closed, without actually landing upstream. If you choose to
2907proceed, please verify that the commit lands upstream as expected."""
thakis@chromium.orgcde3bb62011-01-20 01:16:14 +00002908 print(message)
maruel@chromium.org90541732011-04-01 17:54:18 +00002909 ask_for_data('[Press enter to dcommit or ctrl-C to quit]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002910 return SendUpstream(parser, args, 'dcommit')
2911
2912
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002913@subcommand.usage('[upstream branch to apply against]')
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00002914def CMDland(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002915 """Commits the current changelist via git."""
mmoss@chromium.orgf0e41522015-06-10 19:52:01 +00002916 if settings.GetIsGitSvn() or get_footer_svn_id():
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002917 print('This appears to be an SVN repository.')
2918 print('Are you sure you didn\'t mean \'git cl dcommit\'?')
mmoss@chromium.orgf0e41522015-06-10 19:52:01 +00002919 print('(Ignore if this is the first commit after migrating from svn->git)')
maruel@chromium.org90541732011-04-01 17:54:18 +00002920 ask_for_data('[Press enter to push or ctrl-C to quit]')
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002921 return SendUpstream(parser, args, 'land')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002922
2923
dsinclair@chromium.orgfbed6562015-09-25 21:22:36 +00002924def ParseIssueNum(arg):
2925 """Parses the issue number from args if present otherwise returns None."""
2926 if re.match(r'\d+', arg):
2927 return arg
2928 if arg.startswith('http'):
2929 return re.sub(r'.*/(\d+)/?', r'\1', arg)
2930 return None
2931
2932
2933@subcommand.usage('<patch url or issue id or issue url>')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002934def CMDpatch(parser, args):
marq@chromium.orge5e59002013-10-02 23:21:25 +00002935 """Patches in a code review."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002936 parser.add_option('-b', dest='newbranch',
2937 help='create a new branch off trunk for the patch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002938 parser.add_option('-f', '--force', action='store_true',
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002939 help='with -b, clobber any existing branch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002940 parser.add_option('-d', '--directory', action='store', metavar='DIR',
2941 help='Change to the directory DIR immediately, '
2942 'before doing anything else.')
2943 parser.add_option('--reject', action='store_true',
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00002944 help='failed patches spew .rej files rather than '
2945 'attempting a 3-way merge')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002946 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit',
2947 help="don't commit after patch applies")
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002948 auth.add_auth_options(parser)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002949 (options, args) = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002950 auth_config = auth.extract_auth_config_from_options(options)
2951
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002952 if len(args) != 1:
2953 parser.print_help()
2954 return 1
dsinclair@chromium.orgfbed6562015-09-25 21:22:36 +00002955
2956 issue_arg = ParseIssueNum(args[0])
2957 # The patch URL works because ParseIssueNum won't do any substitution
2958 # as the re.sub pattern fails to match and just returns it.
2959 if issue_arg == None:
2960 parser.print_help()
2961 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002962
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002963 # We don't want uncommitted changes mixed up with the patch.
sbc@chromium.org71437c02015-04-09 19:29:40 +00002964 if git_common.is_dirty_git_tree('patch'):
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002965 return 1
2966
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002967 # TODO(maruel): Use apply_issue.py
ukai@chromium.orge8077812012-02-03 03:41:46 +00002968 # TODO(ukai): use gerrit-cherry-pick for gerrit repository?
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002969
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002970 if options.newbranch:
2971 if options.force:
2972 RunGit(['branch', '-D', options.newbranch],
2973 stderr=subprocess2.PIPE, error_ok=True)
2974 RunGit(['checkout', '-b', options.newbranch,
2975 Changelist().GetUpstreamBranch()])
2976
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002977 return PatchIssue(issue_arg, options.reject, options.nocommit,
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002978 options.directory, auth_config)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002979
2980
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002981def PatchIssue(issue_arg, reject, nocommit, directory, auth_config):
wychen@chromium.orga872e752015-04-28 23:42:18 +00002982 # PatchIssue should never be called with a dirty tree. It is up to the
2983 # caller to check this, but just in case we assert here since the
2984 # consequences of the caller not checking this could be dire.
sbc@chromium.org71437c02015-04-09 19:29:40 +00002985 assert(not git_common.is_dirty_git_tree('apply'))
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002986
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002987 if type(issue_arg) is int or issue_arg.isdigit():
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002988 # Input is an issue id. Figure out the URL.
maruel@chromium.org52424302012-08-29 15:14:30 +00002989 issue = int(issue_arg)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002990 cl = Changelist(issue=issue, auth_config=auth_config)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002991 patchset = cl.GetMostRecentPatchset()
binji@chromium.org0281f522012-09-14 13:37:59 +00002992 patch_data = cl.GetPatchSetDiff(issue, patchset)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002993 else:
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00002994 # Assume it's a URL to the patch. Default to https.
2995 issue_url = gclient_utils.UpgradeToHttps(issue_arg)
kjellander@chromium.org44424542015-06-02 18:35:29 +00002996 match = re.match(r'(.*?)/download/issue(\d+)_(\d+).diff', issue_url)
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002997 if not match:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002998 DieWithError('Must pass an issue ID or full URL for '
2999 '\'Download raw patch set\'')
kjellander@chromium.org44424542015-06-02 18:35:29 +00003000 issue = int(match.group(2))
3001 cl = Changelist(issue=issue, auth_config=auth_config)
3002 cl.rietveld_server = match.group(1)
3003 patchset = int(match.group(3))
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00003004 patch_data = urllib2.urlopen(issue_arg).read()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003005
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003006 # Switch up to the top-level directory, if necessary, in preparation for
3007 # applying the patch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003008 top = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003009 if top:
3010 os.chdir(top)
3011
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003012 # Git patches have a/ at the beginning of source paths. We strip that out
3013 # with a sed script rather than the -p flag to patch so we can feed either
3014 # Git or svn-style patches into the same apply command.
3015 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00003016 try:
3017 patch_data = subprocess2.check_output(
3018 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
3019 except subprocess2.CalledProcessError:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003020 DieWithError('Git patch mungling failed.')
3021 logging.info(patch_data)
bratell@opera.com82b91cd2013-07-09 06:33:41 +00003022
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003023 # We use "git apply" to apply the patch instead of "patch" so that we can
3024 # pick up file adds.
3025 # The --index flag means: also insert into the index (so we catch adds).
bratell@opera.com82b91cd2013-07-09 06:33:41 +00003026 cmd = ['git', 'apply', '--index', '-p0']
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00003027 if directory:
3028 cmd.extend(('--directory', directory))
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003029 if reject:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003030 cmd.append('--reject')
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00003031 elif IsGitVersionAtLeast('1.7.12'):
3032 cmd.append('--3way')
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00003033 try:
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003034 subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
bratell@opera.com82b91cd2013-07-09 06:33:41 +00003035 stdin=patch_data, stdout=subprocess2.VOID)
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00003036 except subprocess2.CalledProcessError:
wychen@chromium.orga872e752015-04-28 23:42:18 +00003037 print 'Failed to apply the patch'
3038 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003039
3040 # If we had an issue, commit the current state and register the issue.
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003041 if not nocommit:
wychen@chromium.org5b3bebb2015-05-28 21:41:43 +00003042 RunGit(['commit', '-m', (cl.GetDescription() + '\n\n' +
3043 'patch from issue %(i)s at patchset '
carlosk@chromium.org71284d92014-11-14 18:12:50 +00003044 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)'
3045 % {'i': issue, 'p': patchset})])
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003046 cl = Changelist(auth_config=auth_config)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003047 cl.SetIssue(issue)
binji@chromium.org0281f522012-09-14 13:37:59 +00003048 cl.SetPatchset(patchset)
pdr@chromium.org98ca6622013-04-09 20:58:40 +00003049 print "Committed patch locally."
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003050 else:
3051 print "Patch applied to index."
3052 return 0
3053
3054
3055def CMDrebase(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003056 """Rebases current branch on top of svn repo."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003057 # Provide a wrapper for git svn rebase to help avoid accidental
3058 # git svn dcommit.
3059 # It's the only command that doesn't use parser at all since we just defer
3060 # execution to git-svn.
bratell@opera.com82b91cd2013-07-09 06:33:41 +00003061
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003062 return RunGitWithCode(['svn', 'rebase'] + args)[1]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003063
3064
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00003065def GetTreeStatus(url=None):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003066 """Fetches the tree status and returns either 'open', 'closed',
3067 'unknown' or 'unset'."""
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00003068 url = url or settings.GetTreeStatusUrl(error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003069 if url:
3070 status = urllib2.urlopen(url).read().lower()
3071 if status.find('closed') != -1 or status == '0':
3072 return 'closed'
3073 elif status.find('open') != -1 or status == '1':
3074 return 'open'
3075 return 'unknown'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003076 return 'unset'
3077
dpranke@chromium.org970c5222011-03-12 00:32:24 +00003078
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003079def GetTreeStatusReason():
3080 """Fetches the tree status from a json url and returns the message
3081 with the reason for the tree to be opened or closed."""
msb@chromium.orgbf1a7ba2011-02-01 16:21:46 +00003082 url = settings.GetTreeStatusUrl()
3083 json_url = urlparse.urljoin(url, '/current?format=json')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003084 connection = urllib2.urlopen(json_url)
3085 status = json.loads(connection.read())
3086 connection.close()
3087 return status['message']
3088
dpranke@chromium.org970c5222011-03-12 00:32:24 +00003089
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00003090def GetBuilderMaster(bot_list):
3091 """For a given builder, fetch the master from AE if available."""
3092 map_url = 'https://builders-map.appspot.com/'
3093 try:
3094 master_map = json.load(urllib2.urlopen(map_url))
3095 except urllib2.URLError as e:
3096 return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' %
3097 (map_url, e))
3098 except ValueError as e:
3099 return None, ('Invalid json string from %s. Error: %s.' % (map_url, e))
3100 if not master_map:
3101 return None, 'Failed to build master map.'
3102
3103 result_master = ''
3104 for bot in bot_list:
3105 builder = bot.split(':', 1)[0]
3106 master_list = master_map.get(builder, [])
3107 if not master_list:
3108 return None, ('No matching master for builder %s.' % builder)
3109 elif len(master_list) > 1:
3110 return None, ('The builder name %s exists in multiple masters %s.' %
3111 (builder, master_list))
3112 else:
3113 cur_master = master_list[0]
3114 if not result_master:
3115 result_master = cur_master
3116 elif result_master != cur_master:
3117 return None, 'The builders do not belong to the same master.'
3118 return result_master, None
3119
3120
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003121def CMDtree(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003122 """Shows the status of the tree."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00003123 _, args = parser.parse_args(args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003124 status = GetTreeStatus()
3125 if 'unset' == status:
3126 print 'You must configure your tree status URL by running "git cl config".'
3127 return 2
3128
3129 print "The tree is %s" % status
3130 print
3131 print GetTreeStatusReason()
3132 if status != 'open':
3133 return 1
3134 return 0
3135
3136
maruel@chromium.org15192402012-09-06 12:38:29 +00003137def CMDtry(parser, args):
sheyang@chromium.orgdb375572015-08-17 19:22:23 +00003138 """Triggers a try job through BuildBucket."""
maruel@chromium.org15192402012-09-06 12:38:29 +00003139 group = optparse.OptionGroup(parser, "Try job options")
3140 group.add_option(
3141 "-b", "--bot", action="append",
3142 help=("IMPORTANT: specify ONE builder per --bot flag. Use it multiple "
3143 "times to specify multiple builders. ex: "
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00003144 "'-b win_rel -b win_layout'. See "
maruel@chromium.org15192402012-09-06 12:38:29 +00003145 "the try server waterfall for the builders name and the tests "
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00003146 "available."))
maruel@chromium.org15192402012-09-06 12:38:29 +00003147 group.add_option(
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003148 "-m", "--master", default='',
iannucci@chromium.org9e849272014-04-04 00:31:55 +00003149 help=("Specify a try master where to run the tries."))
hinoka@chromium.orgfeb9e2a2015-09-25 19:11:09 +00003150 group.add_option( "--luci", action='store_true')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003151 group.add_option(
maruel@chromium.org15192402012-09-06 12:38:29 +00003152 "-r", "--revision",
3153 help="Revision to use for the try job; default: the "
3154 "revision will be determined by the try server; see "
3155 "its waterfall for more info")
3156 group.add_option(
3157 "-c", "--clobber", action="store_true", default=False,
3158 help="Force a clobber before building; e.g. don't do an "
3159 "incremental build")
3160 group.add_option(
3161 "--project",
3162 help="Override which project to use. Projects are defined "
3163 "server-side to define what default bot set to use")
3164 group.add_option(
machenbach@chromium.org45453142015-09-15 08:45:22 +00003165 "-p", "--property", dest="properties", action="append", default=[],
3166 help="Specify generic properties in the form -p key1=value1 -p "
3167 "key2=value2 etc (buildbucket only). The value will be treated as "
3168 "json if decodable, or as string otherwise.")
3169 group.add_option(
maruel@chromium.org15192402012-09-06 12:38:29 +00003170 "-n", "--name", help="Try job name; default to current branch name")
sheyang@google.com6ebaf782015-05-12 19:17:54 +00003171 group.add_option(
sheyang@chromium.orgdb375572015-08-17 19:22:23 +00003172 "--use-rietveld", action="store_true", default=False,
3173 help="Use Rietveld to trigger try jobs.")
3174 group.add_option(
3175 "--buildbucket-host", default='cr-buildbucket.appspot.com',
3176 help="Host of buildbucket. The default host is %default.")
maruel@chromium.org15192402012-09-06 12:38:29 +00003177 parser.add_option_group(group)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003178 auth.add_auth_options(parser)
maruel@chromium.org15192402012-09-06 12:38:29 +00003179 options, args = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003180 auth_config = auth.extract_auth_config_from_options(options)
maruel@chromium.org15192402012-09-06 12:38:29 +00003181
machenbach@chromium.org45453142015-09-15 08:45:22 +00003182 if options.use_rietveld and options.properties:
3183 parser.error('Properties can only be specified with buildbucket')
3184
3185 # Make sure that all properties are prop=value pairs.
3186 bad_params = [x for x in options.properties if '=' not in x]
3187 if bad_params:
3188 parser.error('Got properties with missing "=": %s' % bad_params)
3189
maruel@chromium.org15192402012-09-06 12:38:29 +00003190 if args:
3191 parser.error('Unknown arguments: %s' % args)
3192
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003193 cl = Changelist(auth_config=auth_config)
maruel@chromium.org15192402012-09-06 12:38:29 +00003194 if not cl.GetIssue():
3195 parser.error('Need to upload first')
3196
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00003197 props = cl.GetIssueProperties()
agable@chromium.org787e3062014-08-20 16:31:19 +00003198 if props.get('closed'):
3199 parser.error('Cannot send tryjobs for a closed CL')
3200
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00003201 if props.get('private'):
3202 parser.error('Cannot use trybots with private issue')
3203
maruel@chromium.org15192402012-09-06 12:38:29 +00003204 if not options.name:
3205 options.name = cl.GetBranch()
3206
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00003207 if options.bot and not options.master:
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00003208 options.master, err_msg = GetBuilderMaster(options.bot)
3209 if err_msg:
3210 parser.error('Tryserver master cannot be found because: %s\n'
3211 'Please manually specify the tryserver master'
3212 ', e.g. "-m tryserver.chromium.linux".' % err_msg)
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00003213
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003214 def GetMasterMap():
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00003215 # Process --bot.
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003216 if not options.bot:
3217 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
maruel@chromium.org15192402012-09-06 12:38:29 +00003218
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003219 # Get try masters from PRESUBMIT.py files.
3220 masters = presubmit_support.DoGetTryMasters(
3221 change,
3222 change.LocalPaths(),
3223 settings.GetRoot(),
3224 None,
3225 None,
3226 options.verbose,
3227 sys.stdout)
3228 if masters:
3229 return masters
stip@chromium.org43064fd2013-12-18 20:07:44 +00003230
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003231 # Fall back to deprecated method: get try slaves from PRESUBMIT.py files.
3232 options.bot = presubmit_support.DoGetTrySlaves(
3233 change,
3234 change.LocalPaths(),
3235 settings.GetRoot(),
3236 None,
3237 None,
3238 options.verbose,
3239 sys.stdout)
tandrii@chromium.org71184c02016-01-13 15:18:44 +00003240
3241 if not options.bot:
3242 # Get try masters from cq.cfg if any.
3243 # TODO(tandrii): some (but very few) projects store cq.cfg in different
3244 # location.
3245 cq_cfg = os.path.join(change.RepositoryRoot(),
3246 'infra', 'config', 'cq.cfg')
3247 if os.path.exists(cq_cfg):
3248 masters = {}
machenbach@chromium.org59994802016-01-14 10:10:33 +00003249 cq_masters = commit_queue.get_master_builder_map(
3250 cq_cfg, include_experimental=False, include_triggered=False)
tandrii@chromium.org71184c02016-01-13 15:18:44 +00003251 for master, builders in cq_masters.iteritems():
3252 for builder in builders:
3253 # Skip presubmit builders, because these will fail without LGTM.
3254 if 'presubmit' not in builder.lower():
3255 masters.setdefault(master, {})[builder] = ['defaulttests']
3256 if masters:
3257 return masters
3258
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003259 if not options.bot:
3260 parser.error('No default try builder to try, use --bot')
maruel@chromium.org15192402012-09-06 12:38:29 +00003261
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003262 builders_and_tests = {}
3263 # TODO(machenbach): The old style command-line options don't support
3264 # multiple try masters yet.
3265 old_style = filter(lambda x: isinstance(x, basestring), options.bot)
3266 new_style = filter(lambda x: isinstance(x, tuple), options.bot)
3267
3268 for bot in old_style:
3269 if ':' in bot:
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00003270 parser.error('Specifying testfilter is no longer supported')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003271 elif ',' in bot:
3272 parser.error('Specify one bot per --bot flag')
3273 else:
tandrii@chromium.org3764fa22015-10-21 16:40:40 +00003274 builders_and_tests.setdefault(bot, [])
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003275
3276 for bot, tests in new_style:
3277 builders_and_tests.setdefault(bot, []).extend(tests)
3278
3279 # Return a master map with one master to be backwards compatible. The
3280 # master name defaults to an empty string, which will cause the master
3281 # not to be set on rietveld (deprecated).
3282 return {options.master: builders_and_tests}
3283
3284 masters = GetMasterMap()
stip@chromium.org43064fd2013-12-18 20:07:44 +00003285
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003286 for builders in masters.itervalues():
3287 if any('triggered' in b for b in builders):
3288 print >> sys.stderr, (
3289 'ERROR You are trying to send a job to a triggered bot. This type of'
3290 ' bot requires an\ninitial job from a parent (usually a builder). '
3291 'Instead send your job to the parent.\n'
3292 'Bot list: %s' % builders)
3293 return 1
ilevy@chromium.orgf3b21232012-09-24 20:48:55 +00003294
ilevy@chromium.org36e420b2013-08-06 23:21:12 +00003295 patchset = cl.GetMostRecentPatchset()
3296 if patchset and patchset != cl.GetPatchset():
3297 print(
3298 '\nWARNING Mismatch between local config and server. Did a previous '
3299 'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
3300 'Continuing using\npatchset %s.\n' % patchset)
hinoka@chromium.orgfeb9e2a2015-09-25 19:11:09 +00003301 if options.luci:
3302 trigger_luci_job(cl, masters, options)
3303 elif not options.use_rietveld:
sheyang@google.com6ebaf782015-05-12 19:17:54 +00003304 try:
3305 trigger_try_jobs(auth_config, cl, options, masters, 'git_cl_try')
3306 except BuildbucketResponseException as ex:
3307 print 'ERROR: %s' % ex
fischman@chromium.orgd246c972013-12-21 22:47:38 +00003308 return 1
sheyang@google.com6ebaf782015-05-12 19:17:54 +00003309 except Exception as e:
3310 stacktrace = (''.join(traceback.format_stack()) + traceback.format_exc())
3311 print 'ERROR: Exception when trying to trigger tryjobs: %s\n%s' % (
3312 e, stacktrace)
3313 return 1
3314 else:
3315 try:
3316 cl.RpcServer().trigger_distributed_try_jobs(
3317 cl.GetIssue(), patchset, options.name, options.clobber,
3318 options.revision, masters)
3319 except urllib2.HTTPError as e:
3320 if e.code == 404:
3321 print('404 from rietveld; '
3322 'did you mean to use "git try" instead of "git cl try"?')
3323 return 1
3324 print('Tried jobs on:')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00003325
sheyang@google.com6ebaf782015-05-12 19:17:54 +00003326 for (master, builders) in sorted(masters.iteritems()):
3327 if master:
3328 print 'Master: %s' % master
3329 length = max(len(builder) for builder in builders)
3330 for builder in sorted(builders):
3331 print ' %*s: %s' % (length, builder, ','.join(builders[builder]))
maruel@chromium.org15192402012-09-06 12:38:29 +00003332 return 0
3333
3334
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003335@subcommand.usage('[new upstream branch]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003336def CMDupstream(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003337 """Prints or sets the name of the upstream branch, if any."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00003338 _, args = parser.parse_args(args)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00003339 if len(args) > 1:
maruel@chromium.org27bb3872011-05-30 20:33:19 +00003340 parser.error('Unrecognized args: %s' % ' '.join(args))
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00003341
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003342 cl = Changelist()
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00003343 if args:
3344 # One arg means set upstream branch.
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00003345 branch = cl.GetBranch()
3346 RunGit(['branch', '--set-upstream', branch, args[0]])
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00003347 cl = Changelist()
3348 print "Upstream branch set to " + cl.GetUpstreamBranch()
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00003349
3350 # Clear configured merge-base, if there is one.
3351 git_common.remove_merge_base(branch)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00003352 else:
3353 print cl.GetUpstreamBranch()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003354 return 0
3355
3356
thestig@chromium.org00858c82013-12-02 23:08:03 +00003357def CMDweb(parser, args):
3358 """Opens the current CL in the web browser."""
3359 _, args = parser.parse_args(args)
3360 if args:
3361 parser.error('Unrecognized args: %s' % ' '.join(args))
3362
3363 issue_url = Changelist().GetIssueURL()
3364 if not issue_url:
3365 print >> sys.stderr, 'ERROR No issue to open'
3366 return 1
3367
3368 webbrowser.open(issue_url)
3369 return 0
3370
3371
maruel@chromium.org27bb3872011-05-30 20:33:19 +00003372def CMDset_commit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003373 """Sets the commit bit to trigger the Commit Queue."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003374 auth.add_auth_options(parser)
3375 options, args = parser.parse_args(args)
3376 auth_config = auth.extract_auth_config_from_options(options)
maruel@chromium.org27bb3872011-05-30 20:33:19 +00003377 if args:
3378 parser.error('Unrecognized args: %s' % ' '.join(args))
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003379 cl = Changelist(auth_config=auth_config)
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00003380 props = cl.GetIssueProperties()
3381 if props.get('private'):
3382 parser.error('Cannot set commit on private issue')
maruel@chromium.org27bb3872011-05-30 20:33:19 +00003383 cl.SetFlag('commit', '1')
3384 return 0
3385
3386
groby@chromium.org411034a2013-02-26 15:12:01 +00003387def CMDset_close(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003388 """Closes the issue."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003389 auth.add_auth_options(parser)
3390 options, args = parser.parse_args(args)
3391 auth_config = auth.extract_auth_config_from_options(options)
groby@chromium.org411034a2013-02-26 15:12:01 +00003392 if args:
3393 parser.error('Unrecognized args: %s' % ' '.join(args))
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003394 cl = Changelist(auth_config=auth_config)
groby@chromium.org411034a2013-02-26 15:12:01 +00003395 # Ensure there actually is an issue to close.
3396 cl.GetDescription()
3397 cl.CloseIssue()
3398 return 0
3399
3400
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003401def CMDdiff(parser, args):
wychen@chromium.org37b2ec02015-04-03 00:49:15 +00003402 """Shows differences between local tree and last upload."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003403 auth.add_auth_options(parser)
3404 options, args = parser.parse_args(args)
3405 auth_config = auth.extract_auth_config_from_options(options)
3406 if args:
3407 parser.error('Unrecognized args: %s' % ' '.join(args))
wychen@chromium.org46309bf2015-04-03 21:04:49 +00003408
3409 # Uncommitted (staged and unstaged) changes will be destroyed by
3410 # "git reset --hard" if there are merging conflicts in PatchIssue().
3411 # Staged changes would be committed along with the patch from last
3412 # upload, hence counted toward the "last upload" side in the final
3413 # diff output, and this is not what we want.
sbc@chromium.org71437c02015-04-09 19:29:40 +00003414 if git_common.is_dirty_git_tree('diff'):
wychen@chromium.org46309bf2015-04-03 21:04:49 +00003415 return 1
3416
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003417 cl = Changelist(auth_config=auth_config)
sbc@chromium.org78dc9842013-11-25 18:43:44 +00003418 issue = cl.GetIssue()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003419 branch = cl.GetBranch()
sbc@chromium.org78dc9842013-11-25 18:43:44 +00003420 if not issue:
3421 DieWithError('No issue found for current branch (%s)' % branch)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003422 TMP_BRANCH = 'git-cl-diff'
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003423 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003424
3425 # Create a new branch based on the merge-base
3426 RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch])
3427 try:
3428 # Patch in the latest changes from rietveld.
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003429 rtn = PatchIssue(issue, False, False, None, auth_config)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003430 if rtn != 0:
wychen@chromium.orga872e752015-04-28 23:42:18 +00003431 RunGit(['reset', '--hard'])
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003432 return rtn
3433
wychen@chromium.org06928532015-02-03 02:11:29 +00003434 # Switch back to starting branch and diff against the temporary
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003435 # branch containing the latest rietveld patch.
wychen@chromium.org06928532015-02-03 02:11:29 +00003436 subprocess2.check_call(['git', 'diff', TMP_BRANCH, branch, '--'])
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00003437 finally:
3438 RunGit(['checkout', '-q', branch])
3439 RunGit(['branch', '-D', TMP_BRANCH])
3440
3441 return 0
3442
3443
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003444def CMDowners(parser, args):
wychen@chromium.org37b2ec02015-04-03 00:49:15 +00003445 """Interactively find the owners for reviewing."""
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003446 parser.add_option(
3447 '--no-color',
3448 action='store_true',
3449 help='Use this option to disable color output')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003450 auth.add_auth_options(parser)
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003451 options, args = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003452 auth_config = auth.extract_auth_config_from_options(options)
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003453
3454 author = RunGit(['config', 'user.email']).strip() or None
3455
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00003456 cl = Changelist(auth_config=auth_config)
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003457
3458 if args:
3459 if len(args) > 1:
3460 parser.error('Unknown args')
3461 base_branch = args[0]
3462 else:
3463 # Default to diffing against the common ancestor of the upstream branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003464 base_branch = cl.GetCommonAncestorWithUpstream()
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003465
3466 change = cl.GetChange(base_branch, None)
3467 return owners_finder.OwnersFinder(
3468 [f.LocalPath() for f in
3469 cl.GetChange(base_branch, None).AffectedFiles()],
3470 change.RepositoryRoot(), author,
3471 fopen=file, os_path=os.path, glob=glob.glob,
3472 disable_color=options.no_color).run()
3473
3474
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003475def BuildGitDiffCmd(diff_type, upstream_commit, args):
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003476 """Generates a diff command."""
3477 # Generate diff for the current branch's changes.
3478 diff_cmd = ['diff', '--no-ext-diff', '--no-prefix', diff_type,
3479 upstream_commit, '--' ]
3480
3481 if args:
3482 for arg in args:
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003483 if os.path.isdir(arg) or os.path.isfile(arg):
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003484 diff_cmd.append(arg)
3485 else:
3486 DieWithError('Argument "%s" is not a file or a directory' % arg)
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003487
3488 return diff_cmd
3489
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003490def MatchingFileType(file_name, extensions):
3491 """Returns true if the file name ends with one of the given extensions."""
3492 return bool([ext for ext in extensions if file_name.lower().endswith(ext)])
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003493
enne@chromium.org555cfe42014-01-29 18:21:39 +00003494@subcommand.usage('[files or directories to diff]')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003495def CMDformat(parser, args):
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003496 """Runs auto-formatting tools (clang-format etc.) on the diff."""
thakis@chromium.org9819b1b2014-12-09 21:21:53 +00003497 CLANG_EXTS = ['.cc', '.cpp', '.h', '.mm', '.proto', '.java']
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003498 parser.add_option('--full', action='store_true',
3499 help='Reformat the full content of all touched files')
3500 parser.add_option('--dry-run', action='store_true',
3501 help='Don\'t modify any file on disk.')
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003502 parser.add_option('--python', action='store_true',
3503 help='Format python code with yapf (experimental).')
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003504 parser.add_option('--diff', action='store_true',
3505 help='Print diff to stdout rather than modifying files.')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003506 opts, args = parser.parse_args(args)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003507
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00003508 # git diff generates paths against the root of the repository. Change
3509 # to that directory so clang-format can find files even within subdirs.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003510 rel_base_path = settings.GetRelativeRoot()
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00003511 if rel_base_path:
3512 os.chdir(rel_base_path)
3513
digit@chromium.org29e47272013-05-17 17:01:46 +00003514 # Grab the merge-base commit, i.e. the upstream commit of the current
3515 # branch when it was created or the last time it was rebased. This is
3516 # to cover the case where the user may have called "git fetch origin",
3517 # moving the origin branch to a newer commit, but hasn't rebased yet.
3518 upstream_commit = None
3519 cl = Changelist()
3520 upstream_branch = cl.GetUpstreamBranch()
3521 if upstream_branch:
3522 upstream_commit = RunGit(['merge-base', 'HEAD', upstream_branch])
3523 upstream_commit = upstream_commit.strip()
3524
3525 if not upstream_commit:
3526 DieWithError('Could not find base commit for this branch. '
3527 'Are you in detached state?')
3528
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003529 changed_files_cmd = BuildGitDiffCmd('--name-only', upstream_commit, args)
3530 diff_output = RunGit(changed_files_cmd)
3531 diff_files = diff_output.splitlines()
jkarlin@chromium.orgad21b922016-01-28 17:48:42 +00003532 # Filter out files deleted by this CL
3533 diff_files = [x for x in diff_files if os.path.isfile(x)]
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003534
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003535 clang_diff_files = [x for x in diff_files if MatchingFileType(x, CLANG_EXTS)]
3536 python_diff_files = [x for x in diff_files if MatchingFileType(x, ['.py'])]
3537 dart_diff_files = [x for x in diff_files if MatchingFileType(x, ['.dart'])]
digit@chromium.org29e47272013-05-17 17:01:46 +00003538
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003539 top_dir = os.path.normpath(
3540 RunGit(["rev-parse", "--show-toplevel"]).rstrip('\n'))
3541
3542 # Locate the clang-format binary in the checkout
3543 try:
3544 clang_format_tool = clang_format.FindClangFormatToolInChromiumTree()
3545 except clang_format.NotFoundError, e:
3546 DieWithError(e)
mdempsky@google.comc3b3dc02013-08-05 23:09:49 +00003547
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003548 # Set to 2 to signal to CheckPatchFormatted() that this patch isn't
3549 # formatted. This is used to block during the presubmit.
3550 return_value = 0
3551
digit@chromium.org29e47272013-05-17 17:01:46 +00003552 if opts.full:
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003553 if clang_diff_files:
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003554 cmd = [clang_format_tool]
3555 if not opts.dry_run and not opts.diff:
3556 cmd.append('-i')
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003557 stdout = RunCommand(cmd + clang_diff_files, cwd=top_dir)
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003558 if opts.diff:
3559 sys.stdout.write(stdout)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003560 else:
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003561 env = os.environ.copy()
thakis@chromium.orgbd2a9b92014-09-05 18:54:22 +00003562 env['PATH'] = str(os.path.dirname(clang_format_tool))
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003563 try:
3564 script = clang_format.FindClangFormatScriptInChromiumTree(
3565 'clang-format-diff.py')
3566 except clang_format.NotFoundError, e:
3567 DieWithError(e)
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00003568
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003569 cmd = [sys.executable, script, '-p0']
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003570 if not opts.dry_run and not opts.diff:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003571 cmd.append('-i')
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00003572
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003573 diff_cmd = BuildGitDiffCmd('-U0', upstream_commit, clang_diff_files)
3574 diff_output = RunGit(diff_cmd)
3575
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003576 stdout = RunCommand(cmd, stdin=diff_output, cwd=top_dir, env=env)
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003577 if opts.diff:
3578 sys.stdout.write(stdout)
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003579 if opts.dry_run and len(stdout) > 0:
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003580 return_value = 2
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003581
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003582 # Similar code to above, but using yapf on .py files rather than clang-format
3583 # on C/C++ files
3584 if opts.python:
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003585 yapf_tool = gclient_utils.FindExecutable('yapf')
3586 if yapf_tool is None:
3587 DieWithError('yapf not found in PATH')
3588
3589 if opts.full:
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003590 if python_diff_files:
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003591 cmd = [yapf_tool]
3592 if not opts.dry_run and not opts.diff:
3593 cmd.append('-i')
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003594 stdout = RunCommand(cmd + python_diff_files, cwd=top_dir)
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00003595 if opts.diff:
3596 sys.stdout.write(stdout)
3597 else:
3598 # TODO(sbc): yapf --lines mode still has some issues.
3599 # https://github.com/google/yapf/issues/154
3600 DieWithError('--python currently only works with --full')
3601
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003602 # Dart's formatter does not have the nice property of only operating on
3603 # modified chunks, so hard code full.
3604 if dart_diff_files:
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003605 try:
3606 command = [dart_format.FindDartFmtToolInChromiumTree()]
3607 if not opts.dry_run and not opts.diff:
3608 command.append('-w')
jkarlin@chromium.org6f7fa5e2016-01-20 19:32:21 +00003609 command.extend(dart_diff_files)
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003610
3611 stdout = RunCommand(command, cwd=top_dir, env=env)
3612 if opts.dry_run and stdout:
3613 return_value = 2
3614 except dart_format.NotFoundError as e:
erikcorry@chromium.org3e445022015-12-17 09:07:26 +00003615 print ('Warning: Unable to check Dart code formatting. Dart SDK not ' +
3616 'found in this checkout. Files in other languages are still ' +
3617 'formatted.')
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003618
3619 return return_value
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003620
3621
scottmg@chromium.org84a80c42015-09-22 20:40:37 +00003622@subcommand.usage('<codereview url or issue id>')
3623def CMDcheckout(parser, args):
3624 """Checks out a branch associated with a given Rietveld issue."""
3625 _, args = parser.parse_args(args)
3626
3627 if len(args) != 1:
3628 parser.print_help()
3629 return 1
3630
dsinclair@chromium.orgfbed6562015-09-25 21:22:36 +00003631 target_issue = ParseIssueNum(args[0])
3632 if target_issue == None:
scottmg@chromium.org84a80c42015-09-22 20:40:37 +00003633 parser.print_help()
3634 return 1
3635
3636 key_and_issues = [x.split() for x in RunGit(
3637 ['config', '--local', '--get-regexp', r'branch\..*\.rietveldissue'])
3638 .splitlines()]
3639 branches = []
3640 for key, issue in key_and_issues:
3641 if issue == target_issue:
3642 branches.append(re.sub(r'branch\.(.*)\.rietveldissue', r'\1', key))
3643
3644 if len(branches) == 0:
3645 print 'No branch found for issue %s.' % target_issue
3646 return 1
3647 if len(branches) == 1:
3648 RunGit(['checkout', branches[0]])
3649 else:
3650 print 'Multiple branches match issue %s:' % target_issue
3651 for i in range(len(branches)):
3652 print '%d: %s' % (i, branches[i])
3653 which = raw_input('Choose by index: ')
3654 try:
3655 RunGit(['checkout', branches[int(which)]])
3656 except (IndexError, ValueError):
3657 print 'Invalid selection, not checking out any branch.'
3658 return 1
3659
3660 return 0
3661
3662
maruel@chromium.org29404b52014-09-08 22:58:00 +00003663def CMDlol(parser, args):
3664 # This command is intentionally undocumented.
thakis@chromium.org3421c992014-11-02 02:20:32 +00003665 print zlib.decompress(base64.b64decode(
3666 'eNptkLEOwyAMRHe+wupCIqW57v0Vq84WqWtXyrcXnCBsmgMJ+/SSAxMZgRB6NzE'
3667 'E2ObgCKJooYdu4uAQVffUEoE1sRQLxAcqzd7uK2gmStrll1ucV3uZyaY5sXyDd9'
3668 'JAnN+lAXsOMJ90GANAi43mq5/VeeacylKVgi8o6F1SC63FxnagHfJUTfUYdCR/W'
3669 'Ofe+0dHL7PicpytKP750Fh1q2qnLVof4w8OZWNY'))
maruel@chromium.org29404b52014-09-08 22:58:00 +00003670 return 0
3671
3672
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003673class OptionParser(optparse.OptionParser):
3674 """Creates the option parse and add --verbose support."""
3675 def __init__(self, *args, **kwargs):
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003676 optparse.OptionParser.__init__(
3677 self, *args, prog='git cl', version=__version__, **kwargs)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003678 self.add_option(
3679 '-v', '--verbose', action='count', default=0,
3680 help='Use 2 times for more debugging info')
3681
3682 def parse_args(self, args=None, values=None):
3683 options, args = optparse.OptionParser.parse_args(self, args, values)
3684 levels = [logging.WARNING, logging.INFO, logging.DEBUG]
3685 logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)])
3686 return options, args
3687
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003688
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003689def main(argv):
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003690 if sys.hexversion < 0x02060000:
3691 print >> sys.stderr, (
3692 '\nYour python version %s is unsupported, please upgrade.\n' %
3693 sys.version.split(' ', 1)[0])
3694 return 2
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003695
maruel@chromium.orgddd59412011-11-30 14:20:38 +00003696 # Reload settings.
3697 global settings
3698 settings = Settings()
3699
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003700 colorize_CMDstatus_doc()
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003701 dispatcher = subcommand.CommandDispatcher(__name__)
3702 try:
3703 return dispatcher.execute(OptionParser(), argv)
vadimsh@chromium.orgeed4df32015-04-10 21:30:20 +00003704 except auth.AuthenticationError as e:
3705 DieWithError(str(e))
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003706 except urllib2.HTTPError, e:
3707 if e.code != 500:
3708 raise
3709 DieWithError(
3710 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
3711 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
sbc@chromium.org013731e2015-02-26 18:28:43 +00003712 return 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003713
3714
3715if __name__ == '__main__':
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003716 # These affect sys.stdout so do it outside of main() to simplify mocks in
3717 # unit testing.
maruel@chromium.org6f09cd92011-04-01 16:38:12 +00003718 fix_encoding.fix_encoding()
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003719 colorama.init()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003720 try:
3721 sys.exit(main(sys.argv[1:]))
3722 except KeyboardInterrupt:
3723 sys.stderr.write('interrupted\n')
3724 sys.exit(1)