blob: 4eee3f152853d4a746bc901c465da0355397c3e5 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +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
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +000013import glob
maruel@chromium.org4f6852c2012-04-20 20:39:20 +000014import json
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000015import logging
16import optparse
17import os
maruel@chromium.org1033efd2013-07-23 23:25:09 +000018import Queue
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000019import re
ukai@chromium.org78c4b982012-02-14 02:20:26 +000020import stat
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000021import sys
bauerb@chromium.org27386dd2015-02-16 10:45:39 +000022import tempfile
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000023import textwrap
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000024import urllib2
maruel@chromium.org967c0a82013-06-17 22:52:24 +000025import urlparse
thestig@chromium.org00858c82013-12-02 23:08:03 +000026import webbrowser
thakis@chromium.org3421c992014-11-02 02:20:32 +000027import zlib
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000028
29try:
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000030 import readline # pylint: disable=F0401,W0611
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000031except ImportError:
32 pass
33
maruel@chromium.org2a74d372011-03-29 19:05:50 +000034
maruel@chromium.org2e23ce32013-05-07 12:42:28 +000035from third_party import colorama
maruel@chromium.org2a74d372011-03-29 19:05:50 +000036from third_party import upload
37import breakpad # pylint: disable=W0611
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +000038import clang_format
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +000039import dart_format
maruel@chromium.org6f09cd92011-04-01 16:38:12 +000040import fix_encoding
maruel@chromium.org0e0436a2011-10-25 13:32:41 +000041import gclient_utils
iannucci@chromium.org9e849272014-04-04 00:31:55 +000042import git_common
piman@chromium.org336f9122014-09-04 02:16:55 +000043import owners
iannucci@chromium.org9e849272014-04-04 00:31:55 +000044import owners_finder
maruel@chromium.org2a74d372011-03-29 19:05:50 +000045import presubmit_support
maruel@chromium.orgcab38e92011-04-09 00:30:51 +000046import rietveld
maruel@chromium.org2a74d372011-03-29 19:05:50 +000047import scm
maruel@chromium.org0633fb42013-08-16 20:06:14 +000048import subcommand
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000049import subprocess2
maruel@chromium.org2a74d372011-03-29 19:05:50 +000050import watchlists
51
maruel@chromium.org0633fb42013-08-16 20:06:14 +000052__version__ = '1.0'
maruel@chromium.org2a74d372011-03-29 19:05:50 +000053
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +000054DEFAULT_SERVER = 'https://codereview.appspot.com'
maruel@chromium.org0ba7f962011-01-11 22:13:58 +000055POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000056DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +000057GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit'
sivachandra@chromium.orgaebe87f2012-10-22 20:34:21 +000058CHANGE_ID = 'Change-Id:'
rmistry@google.comc68112d2015-03-03 12:48:06 +000059REFS_THAT_ALIAS_TO_OTHER_REFS = {
60 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master',
61 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master',
62}
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000063
thestig@chromium.org44202a22014-03-11 19:22:18 +000064# Valid extensions for files we want to lint.
65DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
66DEFAULT_LINT_IGNORE_REGEX = r"$^"
67
maruel@chromium.org2e23ce32013-05-07 12:42:28 +000068# Shortcut since it quickly becomes redundant.
69Fore = colorama.Fore
maruel@chromium.org90541732011-04-01 17:54:18 +000070
maruel@chromium.orgddd59412011-11-30 14:20:38 +000071# Initialized in main()
72settings = None
73
74
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000075def DieWithError(message):
dpranke@chromium.org970c5222011-03-12 00:32:24 +000076 print >> sys.stderr, message
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000077 sys.exit(1)
78
79
thestig@chromium.org8b0553c2014-02-11 00:33:37 +000080def GetNoGitPagerEnv():
81 env = os.environ.copy()
82 # 'cat' is a magical git string that disables pagers on all platforms.
83 env['GIT_PAGER'] = 'cat'
84 return env
85
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +000086
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000087def RunCommand(args, error_ok=False, error_message=None, **kwargs):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000088 try:
maruel@chromium.org373af802012-05-25 21:07:33 +000089 return subprocess2.check_output(args, shell=False, **kwargs)
maruel@chromium.org78936cb2013-04-11 00:17:52 +000090 except subprocess2.CalledProcessError as e:
91 logging.debug('Failed running %s', args)
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000092 if not error_ok:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000093 DieWithError(
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000094 'Command "%s" failed.\n%s' % (
95 ' '.join(args), error_message or e.stdout or ''))
96 return e.stdout
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000097
98
99def RunGit(args, **kwargs):
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000100 """Returns stdout."""
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000101 return RunCommand(['git'] + args, **kwargs)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000102
103
enne@chromium.org3b7e15c2014-01-21 17:44:47 +0000104def RunGitWithCode(args, suppress_stderr=False):
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000105 """Returns return code and stdout."""
szager@chromium.org9bb85e22012-06-13 20:28:23 +0000106 try:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +0000107 if suppress_stderr:
108 stderr = subprocess2.VOID
109 else:
110 stderr = sys.stderr
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000111 out, code = subprocess2.communicate(['git'] + args,
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000112 env=GetNoGitPagerEnv(),
enne@chromium.org3b7e15c2014-01-21 17:44:47 +0000113 stdout=subprocess2.PIPE,
114 stderr=stderr)
szager@chromium.org9bb85e22012-06-13 20:28:23 +0000115 return code, out[0]
116 except ValueError:
117 # When the subprocess fails, it returns None. That triggers a ValueError
118 # when trying to unpack the return value into (out, code).
119 return 1, ''
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000120
121
bauerb@chromium.org27386dd2015-02-16 10:45:39 +0000122def RunGitSilent(args):
123 """Returns stdout, suppresses stderr and ingores the return code."""
124 return RunGitWithCode(args, suppress_stderr=True)[1]
125
126
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000127def IsGitVersionAtLeast(min_version):
ilevy@chromium.orgcc56ee42013-07-10 22:16:29 +0000128 prefix = 'git version '
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000129 version = RunGit(['--version']).strip()
ilevy@chromium.orgcc56ee42013-07-10 22:16:29 +0000130 return (version.startswith(prefix) and
131 LooseVersion(version[len(prefix):]) >= LooseVersion(min_version))
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000132
133
maruel@chromium.org90541732011-04-01 17:54:18 +0000134def ask_for_data(prompt):
135 try:
136 return raw_input(prompt)
137 except KeyboardInterrupt:
138 # Hide the exception.
139 sys.exit(1)
140
141
iannucci@chromium.org79540052012-10-19 23:15:26 +0000142def git_set_branch_value(key, value):
143 branch = Changelist().GetBranch()
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +0000144 if not branch:
145 return
146
147 cmd = ['config']
148 if isinstance(value, int):
149 cmd.append('--int')
150 git_key = 'branch.%s.%s' % (branch, key)
151 RunGit(cmd + [git_key, str(value)])
iannucci@chromium.org79540052012-10-19 23:15:26 +0000152
153
154def git_get_branch_default(key, default):
155 branch = Changelist().GetBranch()
156 if branch:
157 git_key = 'branch.%s.%s' % (branch, key)
158 (_, stdout) = RunGitWithCode(['config', '--int', '--get', git_key])
159 try:
160 return int(stdout.strip())
161 except ValueError:
162 pass
163 return default
164
165
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000166def add_git_similarity(parser):
167 parser.add_option(
iannucci@chromium.org79540052012-10-19 23:15:26 +0000168 '--similarity', metavar='SIM', type='int', action='store',
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000169 help='Sets the percentage that a pair of files need to match in order to'
170 ' be considered copies (default 50)')
iannucci@chromium.org79540052012-10-19 23:15:26 +0000171 parser.add_option(
172 '--find-copies', action='store_true',
173 help='Allows git to look for copies.')
174 parser.add_option(
175 '--no-find-copies', action='store_false', dest='find_copies',
176 help='Disallows git from looking for copies.')
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000177
178 old_parser_args = parser.parse_args
179 def Parse(args):
180 options, args = old_parser_args(args)
181
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000182 if options.similarity is None:
iannucci@chromium.org79540052012-10-19 23:15:26 +0000183 options.similarity = git_get_branch_default('git-cl-similarity', 50)
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000184 else:
iannucci@chromium.org79540052012-10-19 23:15:26 +0000185 print('Note: Saving similarity of %d%% in git config.'
186 % options.similarity)
187 git_set_branch_value('git-cl-similarity', options.similarity)
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000188
iannucci@chromium.org79540052012-10-19 23:15:26 +0000189 options.similarity = max(0, min(options.similarity, 100))
190
191 if options.find_copies is None:
192 options.find_copies = bool(
193 git_get_branch_default('git-find-copies', True))
194 else:
195 git_set_branch_value('git-find-copies', int(options.find_copies))
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000196
197 print('Using %d%% similarity for rename/copy detection. '
198 'Override with --similarity.' % options.similarity)
199
200 return options, args
201 parser.parse_args = Parse
202
203
ukai@chromium.org259e4682012-10-25 07:36:33 +0000204def is_dirty_git_tree(cmd):
205 # Make sure index is up-to-date before running diff-index.
206 RunGit(['update-index', '--refresh', '-q'], error_ok=True)
207 dirty = RunGit(['diff-index', '--name-status', 'HEAD'])
208 if dirty:
209 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd
210 print 'Uncommitted files: (git diff-index --name-status HEAD)'
211 print dirty[:4096]
212 if len(dirty) > 4096:
213 print '... (run "git diff-index --name-status HEAD" to see full output).'
214 return True
215 return False
216
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000217
bauerb@chromium.org866276c2011-03-18 20:09:31 +0000218def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
219 """Return the corresponding git ref if |base_url| together with |glob_spec|
220 matches the full |url|.
221
222 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below).
223 """
224 fetch_suburl, as_ref = glob_spec.split(':')
225 if allow_wildcards:
226 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl)
227 if glob_match:
228 # Parse specs like "branches/*/src:refs/remotes/svn/*" or
229 # "branches/{472,597,648}/src:refs/remotes/svn/*".
230 branch_re = re.escape(base_url)
231 if glob_match.group(1):
232 branch_re += '/' + re.escape(glob_match.group(1))
233 wildcard = glob_match.group(2)
234 if wildcard == '*':
235 branch_re += '([^/]*)'
236 else:
237 # Escape and replace surrounding braces with parentheses and commas
238 # with pipe symbols.
239 wildcard = re.escape(wildcard)
240 wildcard = re.sub('^\\\\{', '(', wildcard)
241 wildcard = re.sub('\\\\,', '|', wildcard)
242 wildcard = re.sub('\\\\}$', ')', wildcard)
243 branch_re += wildcard
244 if glob_match.group(3):
245 branch_re += re.escape(glob_match.group(3))
246 match = re.match(branch_re, url)
247 if match:
248 return re.sub('\*$', match.group(1), as_ref)
249
250 # Parse specs like "trunk/src:refs/remotes/origin/trunk".
251 if fetch_suburl:
252 full_url = base_url + '/' + fetch_suburl
253 else:
254 full_url = base_url
255 if full_url == url:
256 return as_ref
257 return None
258
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000259
iannucci@chromium.org79540052012-10-19 23:15:26 +0000260def print_stats(similarity, find_copies, args):
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000261 """Prints statistics about the change to the user."""
262 # --no-ext-diff is broken in some versions of Git, so try to work around
263 # this by overriding the environment (but there is still a problem if the
264 # git config key "diff.external" is used).
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000265 env = GetNoGitPagerEnv()
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000266 if 'GIT_EXTERNAL_DIFF' in env:
267 del env['GIT_EXTERNAL_DIFF']
iannucci@chromium.org79540052012-10-19 23:15:26 +0000268
269 if find_copies:
270 similarity_options = ['--find-copies-harder', '-l100000',
271 '-C%s' % similarity]
272 else:
273 similarity_options = ['-M%s' % similarity]
274
szager@chromium.orgd057f9a2014-05-29 21:09:36 +0000275 try:
276 stdout = sys.stdout.fileno()
277 except AttributeError:
278 stdout = None
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000279 return subprocess2.call(
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000280 ['git',
bratell@opera.comf267b0e2013-05-02 09:11:43 +0000281 'diff', '--no-ext-diff', '--stat'] + similarity_options + args,
szager@chromium.orgd057f9a2014-05-29 21:09:36 +0000282 stdout=stdout, env=env)
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000283
284
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000285class Settings(object):
286 def __init__(self):
287 self.default_server = None
288 self.cc = None
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000289 self.root = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000290 self.is_git_svn = None
291 self.svn_branch = None
292 self.tree_status_url = None
293 self.viewvc_url = None
294 self.updated = False
ukai@chromium.orge8077812012-02-03 03:41:46 +0000295 self.is_gerrit = None
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000296 self.git_editor = None
sheyang@chromium.org152cf832014-06-11 21:37:49 +0000297 self.project = None
kjellander@chromium.org6abc6522014-12-02 07:34:49 +0000298 self.force_https_commit_url = None
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000299 self.pending_ref_prefix = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000300
301 def LazyUpdateIfNeeded(self):
302 """Updates the settings from a codereview.settings file, if available."""
303 if not self.updated:
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000304 # The only value that actually changes the behavior is
305 # autoupdate = "false". Everything else means "true".
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000306 autoupdate = RunGit(['config', 'rietveld.autoupdate'],
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000307 error_ok=True
308 ).strip().lower()
309
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000310 cr_settings_file = FindCodereviewSettingsFile()
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000311 if autoupdate != 'false' and cr_settings_file:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000312 LoadCodereviewSettingsFromFile(cr_settings_file)
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000313 # set updated to True to avoid infinite calling loop
314 # through DownloadHooks
ukai@chromium.org78c4b982012-02-14 02:20:26 +0000315 self.updated = True
316 DownloadHooks(False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000317 self.updated = True
318
319 def GetDefaultServerUrl(self, error_ok=False):
320 if not self.default_server:
321 self.LazyUpdateIfNeeded()
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000322 self.default_server = gclient_utils.UpgradeToHttps(
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000323 self._GetRietveldConfig('server', error_ok=True))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000324 if error_ok:
325 return self.default_server
326 if not self.default_server:
327 error_message = ('Could not find settings file. You must configure '
328 'your review setup by running "git cl config".')
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000329 self.default_server = gclient_utils.UpgradeToHttps(
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000330 self._GetRietveldConfig('server', error_message=error_message))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000331 return self.default_server
332
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000333 @staticmethod
334 def GetRelativeRoot():
335 return RunGit(['rev-parse', '--show-cdup']).strip()
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000336
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000337 def GetRoot(self):
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000338 if self.root is None:
339 self.root = os.path.abspath(self.GetRelativeRoot())
340 return self.root
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000341
342 def GetIsGitSvn(self):
343 """Return true if this repo looks like it's using git-svn."""
344 if self.is_git_svn is None:
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000345 if self.GetPendingRefPrefix():
346 # If PENDING_REF_PREFIX is set then it's a pure git repo no matter what.
347 self.is_git_svn = False
348 else:
349 # If you have any "svn-remote.*" config keys, we think you're using svn.
350 self.is_git_svn = RunGitWithCode(
351 ['config', '--local', '--get-regexp', r'^svn-remote\.'])[0] == 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000352 return self.is_git_svn
353
354 def GetSVNBranch(self):
355 if self.svn_branch is None:
356 if not self.GetIsGitSvn():
357 DieWithError('Repo doesn\'t appear to be a git-svn repo.')
358
359 # Try to figure out which remote branch we're based on.
360 # Strategy:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000361 # 1) iterate through our branch history and find the svn URL.
362 # 2) find the svn-remote that fetches from the URL.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000363
364 # regexp matching the git-svn line that contains the URL.
365 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
366
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000367 # We don't want to go through all of history, so read a line from the
368 # pipe at a time.
369 # The -100 is an arbitrary limit so we don't search forever.
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000370 cmd = ['git', 'log', '-100', '--pretty=medium']
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000371 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE,
372 env=GetNoGitPagerEnv())
maruel@chromium.org740f9d72011-06-10 18:33:10 +0000373 url = None
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000374 for line in proc.stdout:
375 match = git_svn_re.match(line)
376 if match:
377 url = match.group(1)
378 proc.stdout.close() # Cut pipe.
379 break
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000380
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000381 if url:
382 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$')
383 remotes = RunGit(['config', '--get-regexp',
384 r'^svn-remote\..*\.url']).splitlines()
385 for remote in remotes:
386 match = svn_remote_re.match(remote)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000387 if match:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000388 remote = match.group(1)
389 base_url = match.group(2)
szager@chromium.org4ac25532013-12-16 22:07:02 +0000390 rewrite_root = RunGit(
391 ['config', 'svn-remote.%s.rewriteRoot' % remote],
392 error_ok=True).strip()
393 if rewrite_root:
394 base_url = rewrite_root
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000395 fetch_spec = RunGit(
bauerb@chromium.org866276c2011-03-18 20:09:31 +0000396 ['config', 'svn-remote.%s.fetch' % remote],
397 error_ok=True).strip()
398 if fetch_spec:
399 self.svn_branch = MatchSvnGlob(url, base_url, fetch_spec, False)
400 if self.svn_branch:
401 break
402 branch_spec = RunGit(
403 ['config', 'svn-remote.%s.branches' % remote],
404 error_ok=True).strip()
405 if branch_spec:
406 self.svn_branch = MatchSvnGlob(url, base_url, branch_spec, True)
407 if self.svn_branch:
408 break
409 tag_spec = RunGit(
410 ['config', 'svn-remote.%s.tags' % remote],
411 error_ok=True).strip()
412 if tag_spec:
413 self.svn_branch = MatchSvnGlob(url, base_url, tag_spec, True)
414 if self.svn_branch:
415 break
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000416
417 if not self.svn_branch:
418 DieWithError('Can\'t guess svn branch -- try specifying it on the '
419 'command line')
420
421 return self.svn_branch
422
423 def GetTreeStatusUrl(self, error_ok=False):
424 if not self.tree_status_url:
425 error_message = ('You must configure your tree status URL by running '
426 '"git cl config".')
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000427 self.tree_status_url = self._GetRietveldConfig(
428 'tree-status-url', error_ok=error_ok, error_message=error_message)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000429 return self.tree_status_url
430
431 def GetViewVCUrl(self):
432 if not self.viewvc_url:
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000433 self.viewvc_url = self._GetRietveldConfig('viewvc-url', error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000434 return self.viewvc_url
435
rmistry@google.com90752582014-01-14 21:04:50 +0000436 def GetBugPrefix(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000437 return self._GetRietveldConfig('bug-prefix', error_ok=True)
rmistry@google.com90752582014-01-14 21:04:50 +0000438
rmistry@google.com5626a922015-02-26 14:03:30 +0000439 def GetRunPostUploadHook(self):
440 run_post_upload_hook = self._GetRietveldConfig(
441 'run-post-upload-hook', error_ok=True)
442 return run_post_upload_hook == "True"
443
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000444 def GetDefaultCCList(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000445 return self._GetRietveldConfig('cc', error_ok=True)
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000446
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +0000447 def GetDefaultPrivateFlag(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000448 return self._GetRietveldConfig('private', error_ok=True)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +0000449
ukai@chromium.orge8077812012-02-03 03:41:46 +0000450 def GetIsGerrit(self):
451 """Return true if this repo is assosiated with gerrit code review system."""
452 if self.is_gerrit is None:
453 self.is_gerrit = self._GetConfig('gerrit.host', error_ok=True)
454 return self.is_gerrit
455
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000456 def GetGitEditor(self):
457 """Return the editor specified in the git config, or None if none is."""
458 if self.git_editor is None:
459 self.git_editor = self._GetConfig('core.editor', error_ok=True)
460 return self.git_editor or None
461
thestig@chromium.org44202a22014-03-11 19:22:18 +0000462 def GetLintRegex(self):
463 return (self._GetRietveldConfig('cpplint-regex', error_ok=True) or
464 DEFAULT_LINT_REGEX)
465
466 def GetLintIgnoreRegex(self):
467 return (self._GetRietveldConfig('cpplint-ignore-regex', error_ok=True) or
468 DEFAULT_LINT_IGNORE_REGEX)
469
sheyang@chromium.org152cf832014-06-11 21:37:49 +0000470 def GetProject(self):
471 if not self.project:
472 self.project = self._GetRietveldConfig('project', error_ok=True)
473 return self.project
474
kjellander@chromium.org6abc6522014-12-02 07:34:49 +0000475 def GetForceHttpsCommitUrl(self):
476 if not self.force_https_commit_url:
477 self.force_https_commit_url = self._GetRietveldConfig(
478 'force-https-commit-url', error_ok=True)
479 return self.force_https_commit_url
480
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000481 def GetPendingRefPrefix(self):
482 if not self.pending_ref_prefix:
483 self.pending_ref_prefix = self._GetRietveldConfig(
484 'pending-ref-prefix', error_ok=True)
485 return self.pending_ref_prefix
486
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000487 def _GetRietveldConfig(self, param, **kwargs):
488 return self._GetConfig('rietveld.' + param, **kwargs)
489
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000490 def _GetConfig(self, param, **kwargs):
491 self.LazyUpdateIfNeeded()
492 return RunGit(['config', param], **kwargs).strip()
493
494
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000495def ShortBranchName(branch):
496 """Convert a name like 'refs/heads/foo' to just 'foo'."""
497 return branch.replace('refs/heads/', '')
498
499
500class Changelist(object):
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000501 def __init__(self, branchref=None, issue=None):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000502 # Poke settings so we get the "configure your server" message if necessary.
maruel@chromium.org379d07a2011-11-30 14:58:10 +0000503 global settings
504 if not settings:
505 # Happens when git_cl.py is used as a utility library.
506 settings = Settings()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000507 settings.GetDefaultServerUrl()
508 self.branchref = branchref
509 if self.branchref:
510 self.branch = ShortBranchName(self.branchref)
511 else:
512 self.branch = None
513 self.rietveld_server = None
514 self.upstream_branch = None
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000515 self.lookedup_issue = False
516 self.issue = issue or None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000517 self.has_description = False
518 self.description = None
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000519 self.lookedup_patchset = False
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000520 self.patchset = None
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000521 self._rpc_server = None
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000522 self.cc = None
523 self.watchers = ()
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000524 self._remote = None
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000525 self._props = None
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000526
527 def GetCCList(self):
528 """Return the users cc'd on this CL.
529
530 Return is a string suitable for passing to gcl with the --cc flag.
531 """
532 if self.cc is None:
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +0000533 base_cc = settings.GetDefaultCCList()
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000534 more_cc = ','.join(self.watchers)
535 self.cc = ','.join(filter(None, (base_cc, more_cc))) or ''
536 return self.cc
537
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +0000538 def GetCCListWithoutDefault(self):
539 """Return the users cc'd on this CL excluding default ones."""
540 if self.cc is None:
541 self.cc = ','.join(self.watchers)
542 return self.cc
543
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000544 def SetWatchers(self, watchers):
545 """Set the list of email addresses that should be cc'd based on the changed
546 files in this CL.
547 """
548 self.watchers = watchers
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000549
550 def GetBranch(self):
551 """Returns the short branch name, e.g. 'master'."""
552 if not self.branch:
szager@chromium.orgd62c61f2014-10-20 22:33:21 +0000553 branchref = RunGit(['symbolic-ref', 'HEAD'],
554 stderr=subprocess2.VOID, error_ok=True).strip()
555 if not branchref:
556 return None
557 self.branchref = branchref
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000558 self.branch = ShortBranchName(self.branchref)
559 return self.branch
560
561 def GetBranchRef(self):
562 """Returns the full branch name, e.g. 'refs/heads/master'."""
563 self.GetBranch() # Poke the lazy loader.
564 return self.branchref
565
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000566 @staticmethod
567 def FetchUpstreamTuple(branch):
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +0000568 """Returns a tuple containing remote and remote ref,
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000569 e.g. 'origin', 'refs/heads/master'
570 """
571 remote = '.'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000572 upstream_branch = RunGit(['config', 'branch.%s.merge' % branch],
573 error_ok=True).strip()
574 if upstream_branch:
575 remote = RunGit(['config', 'branch.%s.remote' % branch]).strip()
576 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000577 upstream_branch = RunGit(['config', 'rietveld.upstream-branch'],
578 error_ok=True).strip()
579 if upstream_branch:
580 remote = RunGit(['config', 'rietveld.upstream-remote']).strip()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000581 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000582 # Fall back on trying a git-svn upstream branch.
583 if settings.GetIsGitSvn():
584 upstream_branch = settings.GetSVNBranch()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000585 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000586 # Else, try to guess the origin remote.
587 remote_branches = RunGit(['branch', '-r']).split()
588 if 'origin/master' in remote_branches:
589 # Fall back on origin/master if it exits.
590 remote = 'origin'
591 upstream_branch = 'refs/heads/master'
592 elif 'origin/trunk' in remote_branches:
593 # Fall back on origin/trunk if it exists. Generally a shared
594 # git-svn clone
595 remote = 'origin'
596 upstream_branch = 'refs/heads/trunk'
597 else:
598 DieWithError("""Unable to determine default branch to diff against.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000599Either pass complete "git diff"-style arguments, like
600 git cl upload origin/master
601or verify this branch is set up to track another (via the --track argument to
602"git checkout -b ...").""")
603
604 return remote, upstream_branch
605
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000606 def GetCommonAncestorWithUpstream(self):
iannucci@chromium.org9e849272014-04-04 00:31:55 +0000607 return git_common.get_or_create_merge_base(self.GetBranch(),
608 self.GetUpstreamBranch())
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000609
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000610 def GetUpstreamBranch(self):
611 if self.upstream_branch is None:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000612 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000613 if remote is not '.':
mmoss@chromium.orge7585452014-08-24 01:41:11 +0000614 upstream_branch = upstream_branch.replace('refs/heads/',
615 'refs/remotes/%s/' % remote)
616 upstream_branch = upstream_branch.replace('refs/branch-heads/',
617 'refs/remotes/branch-heads/')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000618 self.upstream_branch = upstream_branch
619 return self.upstream_branch
620
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000621 def GetRemoteBranch(self):
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000622 if not self._remote:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000623 remote, branch = None, self.GetBranch()
624 seen_branches = set()
625 while branch not in seen_branches:
626 seen_branches.add(branch)
627 remote, branch = self.FetchUpstreamTuple(branch)
628 branch = ShortBranchName(branch)
629 if remote != '.' or branch.startswith('refs/remotes'):
630 break
631 else:
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000632 remotes = RunGit(['remote'], error_ok=True).split()
633 if len(remotes) == 1:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000634 remote, = remotes
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000635 elif 'origin' in remotes:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000636 remote = 'origin'
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000637 logging.warning('Could not determine which remote this change is '
638 'associated with, so defaulting to "%s". This may '
639 'not be what you want. You may prevent this message '
640 'by running "git svn info" as documented here: %s',
641 self._remote,
642 GIT_INSTRUCTIONS_URL)
643 else:
644 logging.warn('Could not determine which remote this change is '
645 'associated with. You may prevent this message by '
646 'running "git svn info" as documented here: %s',
647 GIT_INSTRUCTIONS_URL)
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000648 branch = 'HEAD'
649 if branch.startswith('refs/remotes'):
650 self._remote = (remote, branch)
mmoss@chromium.orge7585452014-08-24 01:41:11 +0000651 elif branch.startswith('refs/branch-heads/'):
652 self._remote = (remote, branch.replace('refs/', 'refs/remotes/'))
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000653 else:
654 self._remote = (remote, 'refs/remotes/%s/%s' % (remote, branch))
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000655 return self._remote
656
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000657 def GitSanityChecks(self, upstream_git_obj):
658 """Checks git repo status and ensures diff is from local commits."""
659
sbc@chromium.org79706062015-01-14 21:18:12 +0000660 if upstream_git_obj is None:
661 if self.GetBranch() is None:
662 print >> sys.stderr, (
663 'ERROR: unable to dertermine current branch (detached HEAD?)')
664 else:
665 print >> sys.stderr, (
666 'ERROR: no upstream branch')
667 return False
668
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000669 # Verify the commit we're diffing against is in our current branch.
670 upstream_sha = RunGit(['rev-parse', '--verify', upstream_git_obj]).strip()
671 common_ancestor = RunGit(['merge-base', upstream_sha, 'HEAD']).strip()
672 if upstream_sha != common_ancestor:
673 print >> sys.stderr, (
674 'ERROR: %s is not in the current branch. You may need to rebase '
675 'your tracking branch' % upstream_sha)
676 return False
677
678 # List the commits inside the diff, and verify they are all local.
679 commits_in_diff = RunGit(
680 ['rev-list', '^%s' % upstream_sha, 'HEAD']).splitlines()
681 code, remote_branch = RunGitWithCode(['config', 'gitcl.remotebranch'])
682 remote_branch = remote_branch.strip()
683 if code != 0:
684 _, remote_branch = self.GetRemoteBranch()
685
686 commits_in_remote = RunGit(
687 ['rev-list', '^%s' % upstream_sha, remote_branch]).splitlines()
688
689 common_commits = set(commits_in_diff) & set(commits_in_remote)
690 if common_commits:
691 print >> sys.stderr, (
692 'ERROR: Your diff contains %d commits already in %s.\n'
693 'Run "git log --oneline %s..HEAD" to get a list of commits in '
694 'the diff. If you are using a custom git flow, you can override'
695 ' the reference used for this check with "git config '
696 'gitcl.remotebranch <git-ref>".' % (
697 len(common_commits), remote_branch, upstream_git_obj))
698 return False
699 return True
700
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +0000701 def GetGitBaseUrlFromConfig(self):
sheyang@chromium.orga656e702014-05-15 20:43:05 +0000702 """Return the configured base URL from branch.<branchname>.baseurl.
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +0000703
704 Returns None if it is not set.
705 """
sheyang@chromium.orga656e702014-05-15 20:43:05 +0000706 return RunGit(['config', 'branch.%s.base-url' % self.GetBranch()],
707 error_ok=True).strip()
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000708
kjellander@chromium.org6abc6522014-12-02 07:34:49 +0000709 def GetGitSvnRemoteUrl(self):
710 """Return the configured git-svn remote URL parsed from git svn info.
711
712 Returns None if it is not set.
713 """
714 # URL is dependent on the current directory.
715 data = RunGit(['svn', 'info'], cwd=settings.GetRoot())
716 if data:
717 keys = dict(line.split(': ', 1) for line in data.splitlines()
718 if ': ' in line)
719 return keys.get('URL', None)
720 return None
721
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000722 def GetRemoteUrl(self):
723 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'.
724
725 Returns None if there is no remote.
726 """
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000727 remote, _ = self.GetRemoteBranch()
dyen@chromium.org2a13d4f2014-06-13 00:06:37 +0000728 url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip()
729
730 # If URL is pointing to a local directory, it is probably a git cache.
731 if os.path.isdir(url):
732 url = RunGit(['config', 'remote.%s.url' % remote],
733 error_ok=True,
734 cwd=url).strip()
735 return url
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000736
737 def GetIssue(self):
maruel@chromium.org52424302012-08-29 15:14:30 +0000738 """Returns the issue number as a int or None if not set."""
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000739 if self.issue is None and not self.lookedup_issue:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000740 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip()
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000741 self.issue = int(issue) or None if issue else None
742 self.lookedup_issue = True
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000743 return self.issue
744
745 def GetRietveldServer(self):
evan@chromium.org0af9b702012-02-11 00:42:16 +0000746 if not self.rietveld_server:
747 # If we're on a branch then get the server potentially associated
748 # with that branch.
749 if self.GetIssue():
szager@chromium.orgd62c61f2014-10-20 22:33:21 +0000750 rietveld_server_config = self._RietveldServer()
751 if rietveld_server_config:
752 self.rietveld_server = gclient_utils.UpgradeToHttps(RunGit(
753 ['config', rietveld_server_config], error_ok=True).strip())
evan@chromium.org0af9b702012-02-11 00:42:16 +0000754 if not self.rietveld_server:
755 self.rietveld_server = settings.GetDefaultServerUrl()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000756 return self.rietveld_server
757
758 def GetIssueURL(self):
759 """Get the URL for a particular issue."""
dbeam@chromium.org015fd3d2013-06-18 19:02:50 +0000760 if not self.GetIssue():
761 return None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000762 return '%s/%s' % (self.GetRietveldServer(), self.GetIssue())
763
764 def GetDescription(self, pretty=False):
765 if not self.has_description:
766 if self.GetIssue():
maruel@chromium.org52424302012-08-29 15:14:30 +0000767 issue = self.GetIssue()
miket@chromium.org183df1a2012-01-04 19:44:55 +0000768 try:
769 self.description = self.RpcServer().get_description(issue).strip()
maruel@chromium.org85616e02014-07-28 15:37:55 +0000770 except urllib2.HTTPError as e:
miket@chromium.org183df1a2012-01-04 19:44:55 +0000771 if e.code == 404:
772 DieWithError(
773 ('\nWhile fetching the description for issue %d, received a '
774 '404 (not found)\n'
775 'error. It is likely that you deleted this '
776 'issue on the server. If this is the\n'
777 'case, please run\n\n'
778 ' git cl issue 0\n\n'
779 'to clear the association with the deleted issue. Then run '
780 'this command again.') % issue)
781 else:
782 DieWithError(
yujie.mao@intel.comdaee1d32013-12-18 11:55:03 +0000783 '\nFailed to fetch issue description. HTTP error %d' % e.code)
maruel@chromium.org85616e02014-07-28 15:37:55 +0000784 except urllib2.URLError as e:
785 print >> sys.stderr, (
786 'Warning: Failed to retrieve CL description due to network '
787 'failure.')
788 self.description = ''
789
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000790 self.has_description = True
791 if pretty:
792 wrapper = textwrap.TextWrapper()
793 wrapper.initial_indent = wrapper.subsequent_indent = ' '
794 return wrapper.fill(self.description)
795 return self.description
796
797 def GetPatchset(self):
maruel@chromium.org52424302012-08-29 15:14:30 +0000798 """Returns the patchset number as a int or None if not set."""
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000799 if self.patchset is None and not self.lookedup_patchset:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000800 patchset = RunGit(['config', self._PatchsetSetting()],
801 error_ok=True).strip()
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000802 self.patchset = int(patchset) or None if patchset else None
803 self.lookedup_patchset = True
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000804 return self.patchset
805
806 def SetPatchset(self, patchset):
807 """Set this branch's patchset. If patchset=0, clears the patchset."""
808 if patchset:
809 RunGit(['config', self._PatchsetSetting(), str(patchset)])
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000810 self.patchset = patchset
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000811 else:
812 RunGit(['config', '--unset', self._PatchsetSetting()],
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000813 stderr=subprocess2.PIPE, error_ok=True)
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000814 self.patchset = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000815
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000816 def GetMostRecentPatchset(self):
817 return self.GetIssueProperties()['patchsets'][-1]
binji@chromium.org0281f522012-09-14 13:37:59 +0000818
819 def GetPatchSetDiff(self, issue, patchset):
maruel@chromium.org27bb3872011-05-30 20:33:19 +0000820 return self.RpcServer().get(
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000821 '/download/issue%s_%s.diff' % (issue, patchset))
822
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000823 def GetIssueProperties(self):
824 if self._props is None:
825 issue = self.GetIssue()
826 if not issue:
827 self._props = {}
828 else:
829 self._props = self.RpcServer().get_issue_properties(issue, True)
830 return self._props
831
maruel@chromium.orgcf087782013-07-23 13:08:48 +0000832 def GetApprovingReviewers(self):
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000833 return get_approving_reviewers(self.GetIssueProperties())
maruel@chromium.orge52678e2013-04-26 18:34:44 +0000834
apavlov@chromium.orge4efd512014-11-05 09:05:29 +0000835 def AddComment(self, message):
836 return self.RpcServer().add_comment(self.GetIssue(), message)
837
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000838 def SetIssue(self, issue):
839 """Set this branch's issue. If issue=0, clears the issue."""
840 if issue:
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000841 self.issue = issue
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000842 RunGit(['config', self._IssueSetting(), str(issue)])
843 if self.rietveld_server:
844 RunGit(['config', self._RietveldServer(), self.rietveld_server])
845 else:
teravest@chromium.orgd79d4b82013-10-23 20:09:08 +0000846 current_issue = self.GetIssue()
847 if current_issue:
848 RunGit(['config', '--unset', self._IssueSetting()])
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000849 self.issue = None
850 self.SetPatchset(None)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000851
asvitkine@chromium.org15169952011-09-27 14:30:53 +0000852 def GetChange(self, upstream_branch, author):
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000853 if not self.GitSanityChecks(upstream_branch):
854 DieWithError('\nGit sanity check failure')
855
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000856 root = settings.GetRelativeRoot()
bratell@opera.comf267b0e2013-05-02 09:11:43 +0000857 if not root:
858 root = '.'
bauerb@chromium.org512f1ef2011-04-20 15:17:57 +0000859 absroot = os.path.abspath(root)
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000860
861 # We use the sha1 of HEAD as a name of this change.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000862 name = RunGitWithCode(['rev-parse', 'HEAD'])[1].strip()
bauerb@chromium.org512f1ef2011-04-20 15:17:57 +0000863 # Need to pass a relative path for msysgit.
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +0000864 try:
maruel@chromium.org80a9ef12011-12-13 20:44:10 +0000865 files = scm.GIT.CaptureStatus([root], '.', upstream_branch)
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +0000866 except subprocess2.CalledProcessError:
867 DieWithError(
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +0000868 ('\nFailed to diff against upstream branch %s\n\n'
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +0000869 'This branch probably doesn\'t exist anymore. To reset the\n'
870 'tracking branch, please run\n'
871 ' git branch --set-upstream %s trunk\n'
872 'replacing trunk with origin/master or the relevant branch') %
873 (upstream_branch, self.GetBranch()))
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000874
maruel@chromium.org52424302012-08-29 15:14:30 +0000875 issue = self.GetIssue()
876 patchset = self.GetPatchset()
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000877 if issue:
878 description = self.GetDescription()
879 else:
880 # If the change was never uploaded, use the log messages of all commits
881 # up to the branch point, as git cl upload will prefill the description
882 # with these log messages.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000883 args = ['log', '--pretty=format:%s%n%n%b', '%s...' % (upstream_branch)]
884 description = RunGitWithCode(args)[1].strip()
maruel@chromium.org03b3bdc2011-06-14 13:04:12 +0000885
886 if not author:
maruel@chromium.org13f623c2011-07-22 16:02:23 +0000887 author = RunGit(['config', 'user.email']).strip() or None
asvitkine@chromium.org15169952011-09-27 14:30:53 +0000888 return presubmit_support.GitChange(
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000889 name,
890 description,
891 absroot,
892 files,
893 issue,
894 patchset,
agable@chromium.orgea84ef12014-04-30 19:55:12 +0000895 author,
896 upstream=upstream_branch)
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000897
jsbell@chromium.orgb99fbd92014-09-11 17:29:28 +0000898 def GetStatus(self):
899 """Apply a rough heuristic to give a simple summary of an issue's review
900 or CQ status, assuming adherence to a common workflow.
901
902 Returns None if no issue for this branch, or one of the following keywords:
903 * 'error' - error from review tool (including deleted issues)
904 * 'unsent' - not sent for review
905 * 'waiting' - waiting for review
906 * 'reply' - waiting for owner to reply to review
907 * 'lgtm' - LGTM from at least one approved reviewer
908 * 'commit' - in the commit queue
909 * 'closed' - closed
910 """
911 if not self.GetIssue():
912 return None
913
914 try:
915 props = self.GetIssueProperties()
916 except urllib2.HTTPError:
917 return 'error'
918
919 if props.get('closed'):
920 # Issue is closed.
921 return 'closed'
922 if props.get('commit'):
923 # Issue is in the commit queue.
924 return 'commit'
925
926 try:
927 reviewers = self.GetApprovingReviewers()
928 except urllib2.HTTPError:
929 return 'error'
930
931 if reviewers:
932 # Was LGTM'ed.
933 return 'lgtm'
934
935 messages = props.get('messages') or []
936
937 if not messages:
938 # No message was sent.
939 return 'unsent'
940 if messages[-1]['sender'] != props.get('owner_email'):
941 # Non-LGTM reply from non-owner
942 return 'reply'
943 return 'waiting'
944
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +0000945 def RunHook(self, committing, may_prompt, verbose, change):
asvitkine@chromium.org15169952011-09-27 14:30:53 +0000946 """Calls sys.exit() if the hook fails; returns a HookResults otherwise."""
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000947
948 try:
maruel@chromium.orgb0a63912012-01-17 18:10:16 +0000949 return presubmit_support.DoPresubmitChecks(change, committing,
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000950 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin,
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +0000951 default_presubmit=None, may_prompt=may_prompt,
maruel@chromium.org239f4112011-06-03 20:08:23 +0000952 rietveld_obj=self.RpcServer())
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000953 except presubmit_support.PresubmitFailure, e:
954 DieWithError(
955 ('%s\nMaybe your depot_tools is out of date?\n'
956 'If all fails, contact maruel@') % e)
957
maruel@chromium.orgb021b322013-04-08 17:57:29 +0000958 def UpdateDescription(self, description):
959 self.description = description
960 return self.RpcServer().update_description(
961 self.GetIssue(), self.description)
962
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000963 def CloseIssue(self):
maruel@chromium.org607bb1b2011-06-01 23:43:11 +0000964 """Updates the description and closes the issue."""
maruel@chromium.orgb021b322013-04-08 17:57:29 +0000965 return self.RpcServer().close_issue(self.GetIssue())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000966
maruel@chromium.org27bb3872011-05-30 20:33:19 +0000967 def SetFlag(self, flag, value):
968 """Patchset must match."""
969 if not self.GetPatchset():
970 DieWithError('The patchset needs to match. Send another patchset.')
971 try:
972 return self.RpcServer().set_flag(
maruel@chromium.org52424302012-08-29 15:14:30 +0000973 self.GetIssue(), self.GetPatchset(), flag, value)
maruel@chromium.org27bb3872011-05-30 20:33:19 +0000974 except urllib2.HTTPError, e:
975 if e.code == 404:
976 DieWithError('The issue %s doesn\'t exist.' % self.GetIssue())
977 if e.code == 403:
978 DieWithError(
979 ('Access denied to issue %s. Maybe the patchset %s doesn\'t '
980 'match?') % (self.GetIssue(), self.GetPatchset()))
981 raise
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000982
maruel@chromium.orgcab38e92011-04-09 00:30:51 +0000983 def RpcServer(self):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000984 """Returns an upload.RpcServer() to access this review's rietveld instance.
985 """
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000986 if not self._rpc_server:
maruel@chromium.org4bac4b52012-11-27 20:33:52 +0000987 self._rpc_server = rietveld.CachingRietveld(
988 self.GetRietveldServer(), None, None)
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000989 return self._rpc_server
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000990
991 def _IssueSetting(self):
992 """Return the git setting that stores this change's issue."""
993 return 'branch.%s.rietveldissue' % self.GetBranch()
994
995 def _PatchsetSetting(self):
996 """Return the git setting that stores this change's most recent patchset."""
997 return 'branch.%s.rietveldpatchset' % self.GetBranch()
998
999 def _RietveldServer(self):
1000 """Returns the git setting that stores this change's rietveld server."""
szager@chromium.orgd62c61f2014-10-20 22:33:21 +00001001 branch = self.GetBranch()
1002 if branch:
1003 return 'branch.%s.rietveldserver' % branch
1004 return None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001005
1006
1007def GetCodereviewSettingsInteractively():
1008 """Prompt the user for settings."""
ukai@chromium.orge8077812012-02-03 03:41:46 +00001009 # TODO(ukai): ask code review system is rietveld or gerrit?
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001010 server = settings.GetDefaultServerUrl(error_ok=True)
1011 prompt = 'Rietveld server (host[:port])'
1012 prompt += ' [%s]' % (server or DEFAULT_SERVER)
maruel@chromium.org90541732011-04-01 17:54:18 +00001013 newserver = ask_for_data(prompt + ':')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001014 if not server and not newserver:
1015 newserver = DEFAULT_SERVER
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001016 if newserver:
1017 newserver = gclient_utils.UpgradeToHttps(newserver)
1018 if newserver != server:
1019 RunGit(['config', 'rietveld.server', newserver])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001020
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001021 def SetProperty(initial, caption, name, is_url):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001022 prompt = caption
1023 if initial:
1024 prompt += ' ("x" to clear) [%s]' % initial
maruel@chromium.org90541732011-04-01 17:54:18 +00001025 new_val = ask_for_data(prompt + ':')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001026 if new_val == 'x':
1027 RunGit(['config', '--unset-all', 'rietveld.' + name], error_ok=True)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001028 elif new_val:
1029 if is_url:
1030 new_val = gclient_utils.UpgradeToHttps(new_val)
1031 if new_val != initial:
1032 RunGit(['config', 'rietveld.' + name, new_val])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001033
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001034 SetProperty(settings.GetDefaultCCList(), 'CC list', 'cc', False)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001035 SetProperty(settings.GetDefaultPrivateFlag(),
1036 'Private flag (rietveld only)', 'private', False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001037 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL',
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001038 'tree-status-url', False)
1039 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url', True)
rmistry@google.com90752582014-01-14 21:04:50 +00001040 SetProperty(settings.GetBugPrefix(), 'Bug Prefix', 'bug-prefix', False)
rmistry@google.com5626a922015-02-26 14:03:30 +00001041 SetProperty(settings.GetRunPostUploadHook(), 'Run Post Upload Hook',
1042 'run-post-upload-hook', False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001043
1044 # TODO: configure a default branch to diff against, rather than this
1045 # svn-based hackery.
1046
1047
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001048class ChangeDescription(object):
1049 """Contains a parsed form of the change description."""
maruel@chromium.orgc6f60e82013-04-19 17:01:57 +00001050 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$'
agable@chromium.org42c20792013-09-12 17:34:49 +00001051 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$'
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001052
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001053 def __init__(self, description):
agable@chromium.org42c20792013-09-12 17:34:49 +00001054 self._description_lines = (description or '').strip().splitlines()
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001055
agable@chromium.org42c20792013-09-12 17:34:49 +00001056 @property # www.logilab.org/ticket/89786
1057 def description(self): # pylint: disable=E0202
1058 return '\n'.join(self._description_lines)
1059
1060 def set_description(self, desc):
1061 if isinstance(desc, basestring):
1062 lines = desc.splitlines()
1063 else:
1064 lines = [line.rstrip() for line in desc]
1065 while lines and not lines[0]:
1066 lines.pop(0)
1067 while lines and not lines[-1]:
1068 lines.pop(-1)
1069 self._description_lines = lines
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001070
piman@chromium.org336f9122014-09-04 02:16:55 +00001071 def update_reviewers(self, reviewers, add_owners_tbr=False, change=None):
agable@chromium.org42c20792013-09-12 17:34:49 +00001072 """Rewrites the R=/TBR= line(s) as a single line each."""
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001073 assert isinstance(reviewers, list), reviewers
piman@chromium.org336f9122014-09-04 02:16:55 +00001074 if not reviewers and not add_owners_tbr:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001075 return
agable@chromium.org42c20792013-09-12 17:34:49 +00001076 reviewers = reviewers[:]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001077
agable@chromium.org42c20792013-09-12 17:34:49 +00001078 # Get the set of R= and TBR= lines and remove them from the desciption.
1079 regexp = re.compile(self.R_LINE)
1080 matches = [regexp.match(line) for line in self._description_lines]
1081 new_desc = [l for i, l in enumerate(self._description_lines)
1082 if not matches[i]]
1083 self.set_description(new_desc)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001084
agable@chromium.org42c20792013-09-12 17:34:49 +00001085 # Construct new unified R= and TBR= lines.
1086 r_names = []
1087 tbr_names = []
1088 for match in matches:
1089 if not match:
1090 continue
1091 people = cleanup_list([match.group(2).strip()])
1092 if match.group(1) == 'TBR':
1093 tbr_names.extend(people)
1094 else:
1095 r_names.extend(people)
1096 for name in r_names:
1097 if name not in reviewers:
1098 reviewers.append(name)
piman@chromium.org336f9122014-09-04 02:16:55 +00001099 if add_owners_tbr:
1100 owners_db = owners.Database(change.RepositoryRoot(),
1101 fopen=file, os_path=os.path, glob=glob.glob)
1102 all_reviewers = set(tbr_names + reviewers)
1103 missing_files = owners_db.files_not_covered_by(change.LocalPaths(),
1104 all_reviewers)
1105 tbr_names.extend(owners_db.reviewers_for(missing_files,
1106 change.author_email))
agable@chromium.org42c20792013-09-12 17:34:49 +00001107 new_r_line = 'R=' + ', '.join(reviewers) if reviewers else None
1108 new_tbr_line = 'TBR=' + ', '.join(tbr_names) if tbr_names else None
1109
1110 # Put the new lines in the description where the old first R= line was.
1111 line_loc = next((i for i, match in enumerate(matches) if match), -1)
1112 if 0 <= line_loc < len(self._description_lines):
1113 if new_tbr_line:
1114 self._description_lines.insert(line_loc, new_tbr_line)
1115 if new_r_line:
1116 self._description_lines.insert(line_loc, new_r_line)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001117 else:
agable@chromium.org42c20792013-09-12 17:34:49 +00001118 if new_r_line:
1119 self.append_footer(new_r_line)
1120 if new_tbr_line:
1121 self.append_footer(new_tbr_line)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001122
1123 def prompt(self):
1124 """Asks the user to update the description."""
agable@chromium.org42c20792013-09-12 17:34:49 +00001125 self.set_description([
1126 '# Enter a description of the change.',
1127 '# This will be displayed on the codereview site.',
1128 '# The first line will also be used as the subject of the review.',
alancutter@chromium.orgbd1073e2013-06-01 00:34:38 +00001129 '#--------------------This line is 72 characters long'
agable@chromium.org42c20792013-09-12 17:34:49 +00001130 '--------------------',
1131 ] + self._description_lines)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001132
agable@chromium.org42c20792013-09-12 17:34:49 +00001133 regexp = re.compile(self.BUG_LINE)
1134 if not any((regexp.match(line) for line in self._description_lines)):
rmistry@google.com90752582014-01-14 21:04:50 +00001135 self.append_footer('BUG=%s' % settings.GetBugPrefix())
agable@chromium.org42c20792013-09-12 17:34:49 +00001136 content = gclient_utils.RunEditor(self.description, True,
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001137 git_editor=settings.GetGitEditor())
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001138 if not content:
1139 DieWithError('Running editor failed')
agable@chromium.org42c20792013-09-12 17:34:49 +00001140 lines = content.splitlines()
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001141
1142 # Strip off comments.
agable@chromium.org42c20792013-09-12 17:34:49 +00001143 clean_lines = [line.rstrip() for line in lines if not line.startswith('#')]
1144 if not clean_lines:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001145 DieWithError('No CL description, aborting')
agable@chromium.org42c20792013-09-12 17:34:49 +00001146 self.set_description(clean_lines)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001147
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001148 def append_footer(self, line):
agable@chromium.org42c20792013-09-12 17:34:49 +00001149 if self._description_lines:
1150 # Add an empty line if either the last line or the new line isn't a tag.
1151 last_line = self._description_lines[-1]
1152 if (not presubmit_support.Change.TAG_LINE_RE.match(last_line) or
1153 not presubmit_support.Change.TAG_LINE_RE.match(line)):
1154 self._description_lines.append('')
1155 self._description_lines.append(line)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001156
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001157 def get_reviewers(self):
1158 """Retrieves the list of reviewers."""
agable@chromium.org42c20792013-09-12 17:34:49 +00001159 matches = [re.match(self.R_LINE, line) for line in self._description_lines]
1160 reviewers = [match.group(2).strip() for match in matches if match]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001161 return cleanup_list(reviewers)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001162
1163
maruel@chromium.orge52678e2013-04-26 18:34:44 +00001164def get_approving_reviewers(props):
1165 """Retrieves the reviewers that approved a CL from the issue properties with
1166 messages.
1167
1168 Note that the list may contain reviewers that are not committer, thus are not
1169 considered by the CQ.
1170 """
1171 return sorted(
1172 set(
1173 message['sender']
1174 for message in props['messages']
1175 if message['approval'] and message['sender'] in props['reviewers']
1176 )
1177 )
1178
1179
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001180def FindCodereviewSettingsFile(filename='codereview.settings'):
1181 """Finds the given file starting in the cwd and going up.
1182
1183 Only looks up to the top of the repository unless an
1184 'inherit-review-settings-ok' file exists in the root of the repository.
1185 """
1186 inherit_ok_file = 'inherit-review-settings-ok'
1187 cwd = os.getcwd()
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001188 root = settings.GetRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001189 if os.path.isfile(os.path.join(root, inherit_ok_file)):
1190 root = '/'
1191 while True:
1192 if filename in os.listdir(cwd):
1193 if os.path.isfile(os.path.join(cwd, filename)):
1194 return open(os.path.join(cwd, filename))
1195 if cwd == root:
1196 break
1197 cwd = os.path.dirname(cwd)
1198
1199
1200def LoadCodereviewSettingsFromFile(fileobj):
1201 """Parse a codereview.settings file and updates hooks."""
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001202 keyvals = gclient_utils.ParseCodereviewSettingsContent(fileobj.read())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001203
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001204 def SetProperty(name, setting, unset_error_ok=False):
1205 fullname = 'rietveld.' + name
1206 if setting in keyvals:
1207 RunGit(['config', fullname, keyvals[setting]])
1208 else:
1209 RunGit(['config', '--unset-all', fullname], error_ok=unset_error_ok)
1210
1211 SetProperty('server', 'CODE_REVIEW_SERVER')
1212 # Only server setting is required. Other settings can be absent.
1213 # In that case, we ignore errors raised during option deletion attempt.
1214 SetProperty('cc', 'CC_LIST', unset_error_ok=True)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001215 SetProperty('private', 'PRIVATE', unset_error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001216 SetProperty('tree-status-url', 'STATUS', unset_error_ok=True)
1217 SetProperty('viewvc-url', 'VIEW_VC', unset_error_ok=True)
rmistry@google.com90752582014-01-14 21:04:50 +00001218 SetProperty('bug-prefix', 'BUG_PREFIX', unset_error_ok=True)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001219 SetProperty('cpplint-regex', 'LINT_REGEX', unset_error_ok=True)
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00001220 SetProperty('force-https-commit-url', 'FORCE_HTTPS_COMMIT_URL',
1221 unset_error_ok=True)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001222 SetProperty('cpplint-ignore-regex', 'LINT_IGNORE_REGEX', unset_error_ok=True)
sheyang@chromium.org152cf832014-06-11 21:37:49 +00001223 SetProperty('project', 'PROJECT', unset_error_ok=True)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00001224 SetProperty('pending-ref-prefix', 'PENDING_REF_PREFIX', unset_error_ok=True)
rmistry@google.com5626a922015-02-26 14:03:30 +00001225 SetProperty('run-post-upload-hook', 'RUN_POST_UPLOAD_HOOK',
1226 unset_error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001227
ukai@chromium.org7044efc2013-11-28 01:51:21 +00001228 if 'GERRIT_HOST' in keyvals:
ukai@chromium.orge8077812012-02-03 03:41:46 +00001229 RunGit(['config', 'gerrit.host', keyvals['GERRIT_HOST']])
ukai@chromium.orge8077812012-02-03 03:41:46 +00001230
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001231 if 'PUSH_URL_CONFIG' in keyvals and 'ORIGIN_URL_CONFIG' in keyvals:
1232 #should be of the form
1233 #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof
1234 #ORIGIN_URL_CONFIG: http://src.chromium.org/git
1235 RunGit(['config', keyvals['PUSH_URL_CONFIG'],
1236 keyvals['ORIGIN_URL_CONFIG']])
1237
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001238
joshua.lock@intel.com426f69b2012-08-02 23:41:49 +00001239def urlretrieve(source, destination):
1240 """urllib is broken for SSL connections via a proxy therefore we
1241 can't use urllib.urlretrieve()."""
1242 with open(destination, 'w') as f:
1243 f.write(urllib2.urlopen(source).read())
1244
1245
ukai@chromium.org712d6102013-11-27 00:52:58 +00001246def hasSheBang(fname):
1247 """Checks fname is a #! script."""
1248 with open(fname) as f:
1249 return f.read(2).startswith('#!')
1250
1251
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001252def DownloadHooks(force):
1253 """downloads hooks
1254
1255 Args:
1256 force: True to update hooks. False to install hooks if not present.
1257 """
1258 if not settings.GetIsGerrit():
1259 return
ukai@chromium.org712d6102013-11-27 00:52:58 +00001260 src = 'https://gerrit-review.googlesource.com/tools/hooks/commit-msg'
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001261 dst = os.path.join(settings.GetRoot(), '.git', 'hooks', 'commit-msg')
1262 if not os.access(dst, os.X_OK):
1263 if os.path.exists(dst):
1264 if not force:
1265 return
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001266 try:
joshua.lock@intel.com426f69b2012-08-02 23:41:49 +00001267 urlretrieve(src, dst)
ukai@chromium.org712d6102013-11-27 00:52:58 +00001268 if not hasSheBang(dst):
1269 DieWithError('Not a script: %s\n'
1270 'You need to download from\n%s\n'
1271 'into .git/hooks/commit-msg and '
1272 'chmod +x .git/hooks/commit-msg' % (dst, src))
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001273 os.chmod(dst, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
1274 except Exception:
1275 if os.path.exists(dst):
1276 os.remove(dst)
ukai@chromium.org712d6102013-11-27 00:52:58 +00001277 DieWithError('\nFailed to download hooks.\n'
1278 'You need to download from\n%s\n'
1279 'into .git/hooks/commit-msg and '
1280 'chmod +x .git/hooks/commit-msg' % src)
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001281
1282
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001283@subcommand.usage('[repo root containing codereview.settings]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001284def CMDconfig(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001285 """Edits configuration for this tree."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001286
pgervais@chromium.org87884cc2014-01-03 22:23:41 +00001287 parser.add_option('--activate-update', action='store_true',
1288 help='activate auto-updating [rietveld] section in '
1289 '.git/config')
1290 parser.add_option('--deactivate-update', action='store_true',
1291 help='deactivate auto-updating [rietveld] section in '
1292 '.git/config')
1293 options, args = parser.parse_args(args)
1294
1295 if options.deactivate_update:
1296 RunGit(['config', 'rietveld.autoupdate', 'false'])
1297 return
1298
1299 if options.activate_update:
1300 RunGit(['config', '--unset', 'rietveld.autoupdate'])
1301 return
1302
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001303 if len(args) == 0:
1304 GetCodereviewSettingsInteractively()
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001305 DownloadHooks(True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001306 return 0
1307
1308 url = args[0]
1309 if not url.endswith('codereview.settings'):
1310 url = os.path.join(url, 'codereview.settings')
1311
1312 # Load code review settings and download hooks (if available).
1313 LoadCodereviewSettingsFromFile(urllib2.urlopen(url))
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001314 DownloadHooks(True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001315 return 0
1316
1317
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001318def CMDbaseurl(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001319 """Gets or sets base-url for this branch."""
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001320 branchref = RunGit(['symbolic-ref', 'HEAD']).strip()
1321 branch = ShortBranchName(branchref)
1322 _, args = parser.parse_args(args)
1323 if not args:
1324 print("Current base-url:")
1325 return RunGit(['config', 'branch.%s.base-url' % branch],
1326 error_ok=False).strip()
1327 else:
1328 print("Setting base-url to %s" % args[0])
1329 return RunGit(['config', 'branch.%s.base-url' % branch, args[0]],
1330 error_ok=False).strip()
1331
1332
jsbell@chromium.orgb99fbd92014-09-11 17:29:28 +00001333def color_for_status(status):
1334 """Maps a Changelist status to color, for CMDstatus and other tools."""
1335 return {
1336 'unsent': Fore.RED,
1337 'waiting': Fore.BLUE,
1338 'reply': Fore.YELLOW,
1339 'lgtm': Fore.GREEN,
1340 'commit': Fore.MAGENTA,
1341 'closed': Fore.CYAN,
1342 'error': Fore.WHITE,
1343 }.get(status, Fore.WHITE)
1344
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001345def fetch_cl_status(b):
1346 """Fetches information for an issue and returns (branch, issue, color)."""
1347 c = Changelist(branchref=b)
1348 i = c.GetIssueURL()
1349 status = c.GetStatus()
1350 color = color_for_status(status)
1351
1352 if i and (not status or status == 'error'):
1353 # The issue probably doesn't exist anymore.
1354 i += ' (broken)'
1355
1356 return (b, i, color)
1357
1358def get_cl_statuses(branches, fine_grained, max_processes=None):
1359 """Returns a blocking iterable of (branch, issue, color) for given branches.
1360
1361 If fine_grained is true, this will fetch CL statuses from the server.
1362 Otherwise, simply indicate if there's a matching url for the given branches.
1363
1364 If max_processes is specified, it is used as the maximum number of processes
1365 to spawn to fetch CL status from the server. Otherwise 1 process per branch is
1366 spawned.
1367 """
1368 # Silence upload.py otherwise it becomes unwieldly.
1369 upload.verbosity = 0
1370
1371 if fine_grained:
1372 # Process one branch synchronously to work through authentication, then
1373 # spawn processes to process all the other branches in parallel.
1374 if branches:
1375 yield fetch_cl_status(branches[0])
1376
1377 branches_to_fetch = branches[1:]
1378 pool = ThreadPool(
1379 min(max_processes, len(branches_to_fetch))
1380 if max_processes is not None
1381 else len(branches_to_fetch))
1382 for x in pool.imap_unordered(fetch_cl_status, branches_to_fetch):
1383 yield x
1384 else:
1385 # Do not use GetApprovingReviewers(), since it requires an HTTP request.
1386 for b in branches:
1387 c = Changelist(branchref=b)
1388 url = c.GetIssueURL()
1389 yield (b, url, Fore.BLUE if url else Fore.WHITE)
jsbell@chromium.orgb99fbd92014-09-11 17:29:28 +00001390
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001391def CMDstatus(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001392 """Show status of changelists.
1393
1394 Colors are used to tell the state of the CL unless --fast is used:
jsbell@chromium.orgaeab41a2013-12-10 20:01:22 +00001395 - Red not sent for review or broken
1396 - Blue waiting for review
1397 - Yellow waiting for you to reply to review
1398 - Green LGTM'ed
1399 - Magenta in the commit queue
1400 - Cyan was committed, branch can be deleted
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001401
1402 Also see 'git cl comments'.
1403 """
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001404 parser.add_option('--field',
1405 help='print only specific field (desc|id|patch|url)')
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001406 parser.add_option('-f', '--fast', action='store_true',
1407 help='Do not retrieve review status')
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001408 parser.add_option(
1409 '-j', '--maxjobs', action='store', type=int,
1410 help='The maximum number of jobs to use when retrieving review status')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001411 (options, args) = parser.parse_args(args)
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001412 if args:
1413 parser.error('Unsupported args: %s' % args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001414
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001415 if options.field:
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001416 cl = Changelist()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001417 if options.field.startswith('desc'):
1418 print cl.GetDescription()
1419 elif options.field == 'id':
1420 issueid = cl.GetIssue()
1421 if issueid:
1422 print issueid
1423 elif options.field == 'patch':
1424 patchset = cl.GetPatchset()
1425 if patchset:
1426 print patchset
1427 elif options.field == 'url':
1428 url = cl.GetIssueURL()
1429 if url:
1430 print url
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001431 return 0
1432
1433 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads'])
1434 if not branches:
1435 print('No local branch found.')
1436 return 0
1437
1438 changes = (Changelist(branchref=b) for b in branches.splitlines())
jrobbins@chromium.orga4c03052014-04-25 19:06:36 +00001439 branches = [c.GetBranch() for c in changes]
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001440 alignment = max(5, max(len(b) for b in branches))
1441 print 'Branches associated with reviews:'
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001442 output = get_cl_statuses(branches,
1443 fine_grained=not options.fast,
1444 max_processes=options.maxjobs)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001445
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001446 branch_statuses = {}
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001447 alignment = max(5, max(len(ShortBranchName(b)) for b in branches))
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001448 for branch in sorted(branches):
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001449 while branch not in branch_statuses:
1450 b, i, color = output.next()
1451 branch_statuses[b] = (i, color)
1452 issue, color = branch_statuses.pop(branch)
maruel@chromium.org885f6512013-07-27 02:17:26 +00001453 reset = Fore.RESET
1454 if not sys.stdout.isatty():
1455 color = ''
1456 reset = ''
binji@chromium.orgc3d17dd2013-12-19 00:55:31 +00001457 print ' %*s : %s%s%s' % (
maruel@chromium.org885f6512013-07-27 02:17:26 +00001458 alignment, ShortBranchName(branch), color, issue, reset)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001459
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001460 cl = Changelist()
1461 print
1462 print 'Current branch:',
1463 if not cl.GetIssue():
1464 print 'no issue assigned.'
1465 return 0
1466 print cl.GetBranch()
1467 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL())
maruel@chromium.org85616e02014-07-28 15:37:55 +00001468 if not options.fast:
1469 print 'Issue description:'
1470 print cl.GetDescription(pretty=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001471 return 0
1472
1473
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001474def colorize_CMDstatus_doc():
1475 """To be called once in main() to add colors to git cl status help."""
1476 colors = [i for i in dir(Fore) if i[0].isupper()]
1477
1478 def colorize_line(line):
1479 for color in colors:
1480 if color in line.upper():
1481 # Extract whitespaces first and the leading '-'.
1482 indent = len(line) - len(line.lstrip(' ')) + 1
1483 return line[:indent] + getattr(Fore, color) + line[indent:] + Fore.RESET
1484 return line
1485
1486 lines = CMDstatus.__doc__.splitlines()
1487 CMDstatus.__doc__ = '\n'.join(colorize_line(l) for l in lines)
1488
1489
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001490@subcommand.usage('[issue_number]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001491def CMDissue(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001492 """Sets or displays the current code review issue number.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001493
1494 Pass issue number 0 to clear the current issue.
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001495 """
dnj@chromium.org406c4402015-03-03 17:22:28 +00001496 parser.add_option('-r', '--reverse', action='store_true',
1497 help='Lookup the branch(es) for the specified issues. If '
1498 'no issues are specified, all branches with mapped '
1499 'issues will be listed.')
1500 options, args = parser.parse_args(args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001501
dnj@chromium.org406c4402015-03-03 17:22:28 +00001502 if options.reverse:
1503 branches = RunGit(['for-each-ref', 'refs/heads',
1504 '--format=%(refname:short)']).splitlines()
1505
1506 # Reverse issue lookup.
1507 issue_branch_map = {}
1508 for branch in branches:
1509 cl = Changelist(branchref=branch)
1510 issue_branch_map.setdefault(cl.GetIssue(), []).append(branch)
1511 if not args:
1512 args = sorted(issue_branch_map.iterkeys())
1513 for issue in args:
1514 if not issue:
1515 continue
1516 print 'Branch for issue number %s: %s' % (
1517 issue, ', '.join(issue_branch_map.get(int(issue)) or ('None',)))
1518 else:
1519 cl = Changelist()
1520 if len(args) > 0:
1521 try:
1522 issue = int(args[0])
1523 except ValueError:
1524 DieWithError('Pass a number to set the issue or none to list it.\n'
1525 'Maybe you want to run git cl status?')
1526 cl.SetIssue(issue)
1527 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001528 return 0
1529
1530
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001531def CMDcomments(parser, args):
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001532 """Shows or posts review comments for any changelist."""
1533 parser.add_option('-a', '--add-comment', dest='comment',
1534 help='comment to add to an issue')
1535 parser.add_option('-i', dest='issue',
1536 help="review issue id (defaults to current issue)")
1537 options, args = parser.parse_args(args)
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001538
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001539 issue = None
1540 if options.issue:
1541 try:
1542 issue = int(options.issue)
1543 except ValueError:
1544 DieWithError('A review issue id is expected to be a number')
1545
1546 cl = Changelist(issue=issue)
1547
1548 if options.comment:
1549 cl.AddComment(options.comment)
1550 return 0
1551
1552 data = cl.GetIssueProperties()
maruel@chromium.org5cab2d32014-11-11 18:32:41 +00001553 for message in sorted(data.get('messages', []), key=lambda x: x['date']):
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001554 if message['disapproval']:
1555 color = Fore.RED
1556 elif message['approval']:
1557 color = Fore.GREEN
1558 elif message['sender'] == data['owner_email']:
1559 color = Fore.MAGENTA
1560 else:
1561 color = Fore.BLUE
1562 print '\n%s%s %s%s' % (
1563 color, message['date'].split('.', 1)[0], message['sender'],
1564 Fore.RESET)
1565 if message['text'].strip():
1566 print '\n'.join(' ' + l for l in message['text'].splitlines())
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001567 return 0
1568
1569
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001570def CMDdescription(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001571 """Brings up the editor for the current CL's description."""
wychen@chromium.org16ca29c2015-04-02 22:26:55 +00001572 parser.parse_args(args)
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001573 cl = Changelist()
1574 if not cl.GetIssue():
1575 DieWithError('This branch has no associated changelist.')
1576 description = ChangeDescription(cl.GetDescription())
1577 description.prompt()
wychen@chromium.org063e4e52015-04-03 06:51:44 +00001578 if cl.GetDescription() != description.description:
1579 cl.UpdateDescription(description.description)
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001580 return 0
1581
1582
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001583def CreateDescriptionFromLog(args):
1584 """Pulls out the commit log to use as a base for the CL description."""
1585 log_args = []
1586 if len(args) == 1 and not args[0].endswith('.'):
1587 log_args = [args[0] + '..']
1588 elif len(args) == 1 and args[0].endswith('...'):
1589 log_args = [args[0][:-1]]
1590 elif len(args) == 2:
1591 log_args = [args[0] + '..' + args[1]]
1592 else:
1593 log_args = args[:] # Hope for the best!
maruel@chromium.org373af802012-05-25 21:07:33 +00001594 return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001595
1596
thestig@chromium.org44202a22014-03-11 19:22:18 +00001597def CMDlint(parser, args):
1598 """Runs cpplint on the current changelist."""
tzik@chromium.orgf204d4b2014-03-13 07:40:55 +00001599 parser.add_option('--filter', action='append', metavar='-x,+y',
1600 help='Comma-separated list of cpplint\'s category-filters')
1601 (options, args) = parser.parse_args(args)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001602
1603 # Access to a protected member _XX of a client class
1604 # pylint: disable=W0212
1605 try:
1606 import cpplint
1607 import cpplint_chromium
1608 except ImportError:
1609 print "Your depot_tools is missing cpplint.py and/or cpplint_chromium.py."
1610 return 1
1611
1612 # Change the current working directory before calling lint so that it
1613 # shows the correct base.
1614 previous_cwd = os.getcwd()
1615 os.chdir(settings.GetRoot())
1616 try:
1617 cl = Changelist()
1618 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
1619 files = [f.LocalPath() for f in change.AffectedFiles()]
thestig@chromium.org5839eb52014-05-30 16:20:51 +00001620 if not files:
1621 print "Cannot lint an empty CL"
1622 return 1
thestig@chromium.org44202a22014-03-11 19:22:18 +00001623
1624 # Process cpplints arguments if any.
tzik@chromium.orgf204d4b2014-03-13 07:40:55 +00001625 command = args + files
1626 if options.filter:
1627 command = ['--filter=' + ','.join(options.filter)] + command
1628 filenames = cpplint.ParseArguments(command)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001629
1630 white_regex = re.compile(settings.GetLintRegex())
1631 black_regex = re.compile(settings.GetLintIgnoreRegex())
1632 extra_check_functions = [cpplint_chromium.CheckPointerDeclarationWhitespace]
1633 for filename in filenames:
1634 if white_regex.match(filename):
1635 if black_regex.match(filename):
1636 print "Ignoring file %s" % filename
1637 else:
1638 cpplint.ProcessFile(filename, cpplint._cpplint_state.verbose_level,
1639 extra_check_functions)
1640 else:
1641 print "Skipping file %s" % filename
1642 finally:
1643 os.chdir(previous_cwd)
1644 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count
1645 if cpplint._cpplint_state.error_count != 0:
1646 return 1
1647 return 0
1648
1649
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001650def CMDpresubmit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001651 """Runs presubmit tests on the current changelist."""
ilevy@chromium.org375a9022013-01-07 01:12:05 +00001652 parser.add_option('-u', '--upload', action='store_true',
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001653 help='Run upload hook instead of the push/dcommit hook')
ilevy@chromium.org375a9022013-01-07 01:12:05 +00001654 parser.add_option('-f', '--force', action='store_true',
sbc@chromium.org495ad152012-09-04 23:07:42 +00001655 help='Run checks even if tree is dirty')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001656 (options, args) = parser.parse_args(args)
1657
ukai@chromium.org259e4682012-10-25 07:36:33 +00001658 if not options.force and is_dirty_git_tree('presubmit'):
1659 print 'use --force to check even if tree is dirty.'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001660 return 1
1661
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001662 cl = Changelist()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001663 if args:
1664 base_branch = args[0]
1665 else:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00001666 # Default to diffing against the common ancestor of the upstream branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001667 base_branch = cl.GetCommonAncestorWithUpstream()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001668
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00001669 cl.RunHook(
1670 committing=not options.upload,
1671 may_prompt=False,
1672 verbose=options.verbose,
1673 change=cl.GetChange(base_branch, None))
dpranke@chromium.org0a2bb372011-03-25 01:16:22 +00001674 return 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001675
1676
sivachandra@chromium.orgaebe87f2012-10-22 20:34:21 +00001677def AddChangeIdToCommitMessage(options, args):
1678 """Re-commits using the current message, assumes the commit hook is in
1679 place.
1680 """
1681 log_desc = options.message or CreateDescriptionFromLog(args)
1682 git_command = ['commit', '--amend', '-m', log_desc]
1683 RunGit(git_command)
1684 new_log_desc = CreateDescriptionFromLog(args)
1685 if CHANGE_ID in new_log_desc:
1686 print 'git-cl: Added Change-Id to commit message.'
1687 else:
1688 print >> sys.stderr, 'ERROR: Gerrit commit-msg hook not available.'
1689
1690
piman@chromium.org336f9122014-09-04 02:16:55 +00001691def GerritUpload(options, args, cl, change):
ukai@chromium.orge8077812012-02-03 03:41:46 +00001692 """upload the current branch to gerrit."""
1693 # We assume the remote called "origin" is the one we want.
1694 # It is probably not worthwhile to support different workflows.
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001695 gerrit_remote = 'origin'
ukai@chromium.orge8077812012-02-03 03:41:46 +00001696 branch = 'master'
1697 if options.target_branch:
1698 branch = options.target_branch
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001699
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001700 change_desc = ChangeDescription(
1701 options.message or CreateDescriptionFromLog(args))
1702 if not change_desc.description:
ukai@chromium.orge8077812012-02-03 03:41:46 +00001703 print "Description is empty; aborting."
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001704 return 1
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00001705
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001706 if options.squash:
1707 # Try to get the message from a previous upload.
1708 shadow_branch = 'refs/heads/git_cl_uploads/' + cl.GetBranch()
1709 message = RunGitSilent(['show', '--format=%s\n\n%b', '-s', shadow_branch])
1710 if not message:
1711 if not options.force:
1712 change_desc.prompt()
1713
1714 if CHANGE_ID not in change_desc.description:
1715 # Run the commit-msg hook without modifying the head commit by writing
1716 # the commit message to a temporary file and running the hook over it,
1717 # then reading the file back in.
1718 commit_msg_hook = os.path.join(settings.GetRoot(), '.git', 'hooks',
1719 'commit-msg')
1720 file_handle, msg_file = tempfile.mkstemp(text=True,
1721 prefix='commit_msg')
1722 try:
1723 try:
1724 with os.fdopen(file_handle, 'w') as fileobj:
1725 fileobj.write(change_desc.description)
1726 finally:
1727 os.close(file_handle)
1728 RunCommand([commit_msg_hook, msg_file])
1729 change_desc.set_description(gclient_utils.FileRead(msg_file))
1730 finally:
1731 os.remove(msg_file)
1732
1733 if not change_desc.description:
1734 print "Description is empty; aborting."
1735 return 1
1736
1737 message = change_desc.description
1738
1739 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
1740 if remote is '.':
1741 # If our upstream branch is local, we base our squashed commit on its
1742 # squashed version.
1743 parent = ('refs/heads/git_cl_uploads/' +
1744 scm.GIT.ShortBranchName(upstream_branch))
1745
1746 # Verify that the upstream branch has been uploaded too, otherwise Gerrit
1747 # will create additional CLs when uploading.
1748 if (RunGitSilent(['rev-parse', upstream_branch + ':']) !=
1749 RunGitSilent(['rev-parse', parent + ':'])):
1750 print 'Upload upstream branch ' + upstream_branch + ' first.'
1751 return 1
1752 else:
1753 parent = cl.GetCommonAncestorWithUpstream()
1754
1755 tree = RunGit(['rev-parse', 'HEAD:']).strip()
1756 ref_to_push = RunGit(['commit-tree', tree, '-p', parent,
1757 '-m', message]).strip()
1758 else:
1759 if CHANGE_ID not in change_desc.description:
1760 AddChangeIdToCommitMessage(options, args)
1761 ref_to_push = 'HEAD'
1762 parent = '%s/%s' % (gerrit_remote, branch)
1763
1764 commits = RunGitSilent(['rev-list', '%s..%s' % (parent,
1765 ref_to_push)]).splitlines()
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00001766 if len(commits) > 1:
1767 print('WARNING: This will upload %d commits. Run the following command '
1768 'to see which commits will be uploaded: ' % len(commits))
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001769 print('git log %s..%s' % (parent, ref_to_push))
1770 print('You can also use `git squash-branch` to squash these into a single '
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00001771 'commit.')
1772 ask_for_data('About to upload; enter to confirm.')
1773
piman@chromium.org336f9122014-09-04 02:16:55 +00001774 if options.reviewers or options.tbr_owners:
1775 change_desc.update_reviewers(options.reviewers, options.tbr_owners, change)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001776
ukai@chromium.orge8077812012-02-03 03:41:46 +00001777 receive_options = []
1778 cc = cl.GetCCList().split(',')
1779 if options.cc:
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00001780 cc.extend(options.cc)
ukai@chromium.orge8077812012-02-03 03:41:46 +00001781 cc = filter(None, cc)
1782 if cc:
1783 receive_options += ['--cc=' + email for email in cc]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001784 if change_desc.get_reviewers():
1785 receive_options.extend(
1786 '--reviewer=' + email for email in change_desc.get_reviewers())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001787
ukai@chromium.orge8077812012-02-03 03:41:46 +00001788 git_command = ['push']
1789 if receive_options:
ukai@chromium.org19bbfa22012-02-03 16:18:11 +00001790 git_command.append('--receive-pack=git receive-pack %s' %
ukai@chromium.orge8077812012-02-03 03:41:46 +00001791 ' '.join(receive_options))
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001792 git_command += [gerrit_remote, ref_to_push + ':refs/for/' + branch]
ukai@chromium.orge8077812012-02-03 03:41:46 +00001793 RunGit(git_command)
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001794
1795 if options.squash:
1796 head = RunGit(['rev-parse', 'HEAD']).strip()
1797 RunGit(['update-ref', '-m', 'Uploaded ' + head, shadow_branch, ref_to_push])
1798
ukai@chromium.orge8077812012-02-03 03:41:46 +00001799 # TODO(ukai): parse Change-Id: and set issue number?
1800 return 0
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001801
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001802
wittman@chromium.org455dc922015-01-26 20:15:50 +00001803def GetTargetRef(remote, remote_branch, target_branch, pending_prefix):
1804 """Computes the remote branch ref to use for the CL.
1805
1806 Args:
1807 remote (str): The git remote for the CL.
1808 remote_branch (str): The git remote branch for the CL.
1809 target_branch (str): The target branch specified by the user.
1810 pending_prefix (str): The pending prefix from the settings.
1811 """
1812 if not (remote and remote_branch):
1813 return None
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001814
wittman@chromium.org455dc922015-01-26 20:15:50 +00001815 if target_branch:
1816 # Cannonicalize branch references to the equivalent local full symbolic
1817 # refs, which are then translated into the remote full symbolic refs
1818 # below.
1819 if '/' not in target_branch:
1820 remote_branch = 'refs/remotes/%s/%s' % (remote, target_branch)
1821 else:
1822 prefix_replacements = (
1823 ('^((refs/)?remotes/)?branch-heads/', 'refs/remotes/branch-heads/'),
1824 ('^((refs/)?remotes/)?%s/' % remote, 'refs/remotes/%s/' % remote),
1825 ('^(refs/)?heads/', 'refs/remotes/%s/' % remote),
1826 )
1827 match = None
1828 for regex, replacement in prefix_replacements:
1829 match = re.search(regex, target_branch)
1830 if match:
1831 remote_branch = target_branch.replace(match.group(0), replacement)
1832 break
1833 if not match:
1834 # This is a branch path but not one we recognize; use as-is.
1835 remote_branch = target_branch
rmistry@google.comc68112d2015-03-03 12:48:06 +00001836 elif remote_branch in REFS_THAT_ALIAS_TO_OTHER_REFS:
1837 # Handle the refs that need to land in different refs.
1838 remote_branch = REFS_THAT_ALIAS_TO_OTHER_REFS[remote_branch]
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001839
wittman@chromium.org455dc922015-01-26 20:15:50 +00001840 # Create the true path to the remote branch.
1841 # Does the following translation:
1842 # * refs/remotes/origin/refs/diff/test -> refs/diff/test
1843 # * refs/remotes/origin/master -> refs/heads/master
1844 # * refs/remotes/branch-heads/test -> refs/branch-heads/test
1845 if remote_branch.startswith('refs/remotes/%s/refs/' % remote):
1846 remote_branch = remote_branch.replace('refs/remotes/%s/' % remote, '')
1847 elif remote_branch.startswith('refs/remotes/%s/' % remote):
1848 remote_branch = remote_branch.replace('refs/remotes/%s/' % remote,
1849 'refs/heads/')
1850 elif remote_branch.startswith('refs/remotes/branch-heads'):
1851 remote_branch = remote_branch.replace('refs/remotes/', 'refs/')
1852 # If a pending prefix exists then replace refs/ with it.
1853 if pending_prefix:
1854 remote_branch = remote_branch.replace('refs/', pending_prefix)
1855 return remote_branch
1856
1857
piman@chromium.org336f9122014-09-04 02:16:55 +00001858def RietveldUpload(options, args, cl, change):
ukai@chromium.orge8077812012-02-03 03:41:46 +00001859 """upload the patch to rietveld."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001860 upload_args = ['--assume_yes'] # Don't ask about untracked files.
1861 upload_args.extend(['--server', cl.GetRietveldServer()])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001862 if options.emulate_svn_auto_props:
1863 upload_args.append('--emulate_svn_auto_props')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001864
1865 change_desc = None
1866
pgervais@chromium.org91141372014-01-09 23:27:20 +00001867 if options.email is not None:
1868 upload_args.extend(['--email', options.email])
1869
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001870 if cl.GetIssue():
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00001871 if options.title:
1872 upload_args.extend(['--title', options.title])
rogerta@chromium.orgafadfca2013-05-29 14:15:53 +00001873 if options.message:
1874 upload_args.extend(['--message', options.message])
maruel@chromium.org52424302012-08-29 15:14:30 +00001875 upload_args.extend(['--issue', str(cl.GetIssue())])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001876 print ("This branch is associated with issue %s. "
1877 "Adding patch to that issue." % cl.GetIssue())
1878 else:
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00001879 if options.title:
1880 upload_args.extend(['--title', options.title])
rogerta@chromium.org43e34f02013-03-25 14:52:48 +00001881 message = options.title or options.message or CreateDescriptionFromLog(args)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001882 change_desc = ChangeDescription(message)
piman@chromium.org336f9122014-09-04 02:16:55 +00001883 if options.reviewers or options.tbr_owners:
1884 change_desc.update_reviewers(options.reviewers,
1885 options.tbr_owners,
1886 change)
maruel@chromium.org71e12a92012-02-14 02:34:15 +00001887 if not options.force:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001888 change_desc.prompt()
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001889
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001890 if not change_desc.description:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001891 print "Description is empty; aborting."
1892 return 1
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001893
maruel@chromium.org71e12a92012-02-14 02:34:15 +00001894 upload_args.extend(['--message', change_desc.description])
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001895 if change_desc.get_reviewers():
1896 upload_args.append('--reviewers=' + ','.join(change_desc.get_reviewers()))
maruel@chromium.orga3353652011-11-30 14:26:57 +00001897 if options.send_mail:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001898 if not change_desc.get_reviewers():
maruel@chromium.orga3353652011-11-30 14:26:57 +00001899 DieWithError("Must specify reviewers to send email.")
1900 upload_args.append('--send_mail')
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +00001901
1902 # We check this before applying rietveld.private assuming that in
1903 # rietveld.cc only addresses which we can send private CLs to are listed
1904 # if rietveld.private is set, and so we should ignore rietveld.cc only when
1905 # --private is specified explicitly on the command line.
1906 if options.private:
1907 logging.warn('rietveld.cc is ignored since private flag is specified. '
1908 'You need to review and add them manually if necessary.')
1909 cc = cl.GetCCListWithoutDefault()
1910 else:
1911 cc = cl.GetCCList()
1912 cc = ','.join(filter(None, (cc, ','.join(options.cc))))
maruel@chromium.orgb2a7c332011-02-25 20:30:37 +00001913 if cc:
1914 upload_args.extend(['--cc', cc])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001915
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001916 if options.private or settings.GetDefaultPrivateFlag() == "True":
1917 upload_args.append('--private')
1918
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00001919 upload_args.extend(['--git_similarity', str(options.similarity)])
iannucci@chromium.org79540052012-10-19 23:15:26 +00001920 if not options.find_copies:
1921 upload_args.extend(['--git_no_find_copies'])
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00001922
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001923 # Include the upstream repo's URL in the change -- this is useful for
1924 # projects that have their source spread across multiple repos.
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001925 remote_url = cl.GetGitBaseUrlFromConfig()
1926 if not remote_url:
1927 if settings.GetIsGitSvn():
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00001928 remote_url = cl.GetGitSvnRemoteUrl()
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001929 else:
jam@chromium.org80c51ae2014-10-17 18:43:02 +00001930 if cl.GetRemoteUrl() and '/' in cl.GetUpstreamBranch():
1931 remote_url = (cl.GetRemoteUrl() + '@'
1932 + cl.GetUpstreamBranch().split('/')[-1])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001933 if remote_url:
1934 upload_args.extend(['--base_url', remote_url])
rmistry@google.comd1e37582014-12-10 20:58:24 +00001935 remote, remote_branch = cl.GetRemoteBranch()
wittman@chromium.org455dc922015-01-26 20:15:50 +00001936 target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
1937 settings.GetPendingRefPrefix())
1938 if target_ref:
1939 upload_args.extend(['--target_ref', target_ref])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001940
sheyang@chromium.org152cf832014-06-11 21:37:49 +00001941 project = settings.GetProject()
1942 if project:
1943 upload_args.extend(['--project', project])
1944
rmistry@google.comef966222015-04-07 11:15:01 +00001945 if options.cq_dry_run:
1946 upload_args.extend(['--cq_dry_run'])
1947
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001948 try:
ilevy@chromium.org82880192012-11-26 15:41:57 +00001949 upload_args = ['upload'] + upload_args + args
1950 logging.info('upload.RealMain(%s)', upload_args)
1951 issue, patchset = upload.RealMain(upload_args)
maruel@chromium.org911fce12013-07-29 23:01:13 +00001952 issue = int(issue)
1953 patchset = int(patchset)
maruel@chromium.org9ce0dff2011-04-04 17:56:50 +00001954 except KeyboardInterrupt:
1955 sys.exit(1)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001956 except:
1957 # If we got an exception after the user typed a description for their
1958 # change, back up the description before re-raising.
1959 if change_desc:
1960 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
1961 print '\nGot exception while uploading -- saving description to %s\n' \
1962 % backup_path
1963 backup_file = open(backup_path, 'w')
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001964 backup_file.write(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001965 backup_file.close()
1966 raise
1967
1968 if not cl.GetIssue():
1969 cl.SetIssue(issue)
1970 cl.SetPatchset(patchset)
maruel@chromium.org27bb3872011-05-30 20:33:19 +00001971
1972 if options.use_commit_queue:
1973 cl.SetFlag('commit', '1')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001974 return 0
1975
1976
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00001977def cleanup_list(l):
1978 """Fixes a list so that comma separated items are put as individual items.
1979
1980 So that "--reviewers joe@c,john@c --reviewers joa@c" results in
1981 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']).
1982 """
1983 items = sum((i.split(',') for i in l), [])
1984 stripped_items = (i.strip() for i in items)
1985 return sorted(filter(None, stripped_items))
1986
1987
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001988@subcommand.usage('[args to "git diff"]')
ukai@chromium.orge8077812012-02-03 03:41:46 +00001989def CMDupload(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001990 """Uploads the current changelist to codereview."""
ukai@chromium.orge8077812012-02-03 03:41:46 +00001991 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
1992 help='bypass upload presubmit hook')
brettw@chromium.orgb65c43c2013-06-10 22:04:49 +00001993 parser.add_option('--bypass-watchlists', action='store_true',
1994 dest='bypass_watchlists',
1995 help='bypass watchlists auto CC-ing reviewers')
ukai@chromium.orge8077812012-02-03 03:41:46 +00001996 parser.add_option('-f', action='store_true', dest='force',
1997 help="force yes to questions (don't prompt)")
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00001998 parser.add_option('-m', dest='message', help='message for patchset')
1999 parser.add_option('-t', dest='title', help='title for patchset')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002000 parser.add_option('-r', '--reviewers',
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002001 action='append', default=[],
ukai@chromium.orge8077812012-02-03 03:41:46 +00002002 help='reviewer email addresses')
2003 parser.add_option('--cc',
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002004 action='append', default=[],
ukai@chromium.orge8077812012-02-03 03:41:46 +00002005 help='cc email addresses')
adamk@chromium.org36f47302013-04-05 01:08:31 +00002006 parser.add_option('-s', '--send-mail', action='store_true',
ukai@chromium.orge8077812012-02-03 03:41:46 +00002007 help='send email to reviewer immediately')
pgervais@chromium.orgb9f27512014-08-08 15:52:33 +00002008 parser.add_option('--emulate_svn_auto_props',
2009 '--emulate-svn-auto-props',
2010 action="store_true",
ukai@chromium.orge8077812012-02-03 03:41:46 +00002011 dest="emulate_svn_auto_props",
2012 help="Emulate Subversion's auto properties feature.")
ukai@chromium.orge8077812012-02-03 03:41:46 +00002013 parser.add_option('-c', '--use-commit-queue', action='store_true',
2014 help='tell the commit queue to commit this patchset')
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00002015 parser.add_option('--private', action='store_true',
2016 help='set the review private (rietveld only)')
ukai@chromium.org8ef7ab22012-11-28 04:24:52 +00002017 parser.add_option('--target_branch',
pgervais@chromium.orgb9f27512014-08-08 15:52:33 +00002018 '--target-branch',
wittman@chromium.org455dc922015-01-26 20:15:50 +00002019 metavar='TARGET',
2020 help='Apply CL to remote ref TARGET. ' +
2021 'Default: remote branch head, or master')
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002022 parser.add_option('--squash', action='store_true',
2023 help='Squash multiple commits into one (Gerrit only)')
pgervais@chromium.org91141372014-01-09 23:27:20 +00002024 parser.add_option('--email', default=None,
2025 help='email address to use to connect to Rietveld')
piman@chromium.org336f9122014-09-04 02:16:55 +00002026 parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true',
2027 help='add a set of OWNERS to TBR')
rmistry@google.comef966222015-04-07 11:15:01 +00002028 parser.add_option('--cq-dry-run', dest='cq_dry_run', action='store_true',
2029 help='Send the patchset to do a CQ dry run right after '
2030 'upload.')
pgervais@chromium.org91141372014-01-09 23:27:20 +00002031
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002032 add_git_similarity(parser)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002033 (options, args) = parser.parse_args(args)
2034
ukai@chromium.org259e4682012-10-25 07:36:33 +00002035 if is_dirty_git_tree('upload'):
ukai@chromium.orge8077812012-02-03 03:41:46 +00002036 return 1
2037
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002038 options.reviewers = cleanup_list(options.reviewers)
2039 options.cc = cleanup_list(options.cc)
2040
ukai@chromium.orge8077812012-02-03 03:41:46 +00002041 cl = Changelist()
2042 if args:
2043 # TODO(ukai): is it ok for gerrit case?
2044 base_branch = args[0]
2045 else:
luqui@chromium.org64e14362015-01-07 00:29:29 +00002046 if cl.GetBranch() is None:
2047 DieWithError('Can\'t upload from detached HEAD state. Get on a branch!')
2048
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00002049 # Default to diffing against common ancestor of upstream branch
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002050 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org5e07e062013-02-28 23:55:44 +00002051 args = [base_branch, 'HEAD']
ukai@chromium.orge8077812012-02-03 03:41:46 +00002052
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002053 # Apply watchlists on upload.
2054 change = cl.GetChange(base_branch, None)
2055 watchlist = watchlists.Watchlists(change.RepositoryRoot())
2056 files = [f.LocalPath() for f in change.AffectedFiles()]
brettw@chromium.orgb65c43c2013-06-10 22:04:49 +00002057 if not options.bypass_watchlists:
2058 cl.SetWatchers(watchlist.GetWatchersForPaths(files))
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002059
ukai@chromium.orge8077812012-02-03 03:41:46 +00002060 if not options.bypass_hooks:
piman@chromium.org336f9122014-09-04 02:16:55 +00002061 if options.reviewers or options.tbr_owners:
isherman@chromium.orgb5cded62014-03-25 17:47:57 +00002062 # Set the reviewer list now so that presubmit checks can access it.
2063 change_description = ChangeDescription(change.FullDescriptionText())
piman@chromium.org336f9122014-09-04 02:16:55 +00002064 change_description.update_reviewers(options.reviewers,
2065 options.tbr_owners,
2066 change)
isherman@chromium.orgb5cded62014-03-25 17:47:57 +00002067 change.SetDescriptionText(change_description.description)
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002068 hook_results = cl.RunHook(committing=False,
ukai@chromium.orge8077812012-02-03 03:41:46 +00002069 may_prompt=not options.force,
2070 verbose=options.verbose,
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002071 change=change)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002072 if not hook_results.should_continue():
2073 return 1
2074 if not options.reviewers and hook_results.reviewers:
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002075 options.reviewers = hook_results.reviewers.split(',')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002076
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002077 if cl.GetIssue():
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002078 latest_patchset = cl.GetMostRecentPatchset()
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002079 local_patchset = cl.GetPatchset()
dmikurube@chromium.org07d149f2013-04-03 11:40:23 +00002080 if latest_patchset and local_patchset and local_patchset != latest_patchset:
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002081 print ('The last upload made from this repository was patchset #%d but '
2082 'the most recent patchset on the server is #%d.'
2083 % (local_patchset, latest_patchset))
koz@chromium.orgc7192782013-04-09 23:28:46 +00002084 print ('Uploading will still work, but if you\'ve uploaded to this issue '
2085 'from another machine or branch the patch you\'re uploading now '
2086 'might not include those changes.')
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002087 ask_for_data('About to upload; enter to confirm.')
2088
iannucci@chromium.org79540052012-10-19 23:15:26 +00002089 print_stats(options.similarity, options.find_copies, args)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002090 if settings.GetIsGerrit():
piman@chromium.org336f9122014-09-04 02:16:55 +00002091 return GerritUpload(options, args, cl, change)
2092 ret = RietveldUpload(options, args, cl, change)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00002093 if not ret:
rogerta@chromium.org4a6cd042013-04-12 15:40:42 +00002094 git_set_branch_value('last-upload-hash',
2095 RunGit(['rev-parse', 'HEAD']).strip())
rmistry@google.com5626a922015-02-26 14:03:30 +00002096 # Run post upload hooks, if specified.
2097 if settings.GetRunPostUploadHook():
2098 presubmit_support.DoPostUploadExecuter(
2099 change,
2100 cl,
2101 settings.GetRoot(),
2102 options.verbose,
2103 sys.stdout)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00002104
2105 return ret
ukai@chromium.orge8077812012-02-03 03:41:46 +00002106
2107
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002108def IsSubmoduleMergeCommit(ref):
2109 # When submodules are added to the repo, we expect there to be a single
2110 # non-git-svn merge commit at remote HEAD with a signature comment.
2111 pattern = '^SVN changes up to revision [0-9]*$'
szager@chromium.orge84b7542012-06-15 21:26:58 +00002112 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref]
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002113 return RunGit(cmd) != ''
2114
2115
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002116def SendUpstream(parser, args, cmd):
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00002117 """Common code for CMDland and CmdDCommit
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002118
iannucci@chromium.org5724c962014-04-11 09:32:56 +00002119 Squashes branch into a single commit.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002120 Updates changelog with metadata (e.g. pointer to review).
2121 Pushes/dcommits the code upstream.
2122 Updates review and closes.
2123 """
2124 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
2125 help='bypass upload presubmit hook')
2126 parser.add_option('-m', dest='message',
2127 help="override review description")
2128 parser.add_option('-f', action='store_true', dest='force',
2129 help="force yes to questions (don't prompt)")
2130 parser.add_option('-c', dest='contributor',
2131 help="external contributor for patch (appended to " +
2132 "description and used as author for git). Should be " +
2133 "formatted as 'First Last <email@example.com>'")
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002134 add_git_similarity(parser)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002135 (options, args) = parser.parse_args(args)
2136 cl = Changelist()
2137
iannucci@chromium.org5724c962014-04-11 09:32:56 +00002138 current = cl.GetBranch()
2139 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
2140 if not settings.GetIsGitSvn() and remote == '.':
2141 print
2142 print 'Attempting to push branch %r into another local branch!' % current
2143 print
2144 print 'Either reparent this branch on top of origin/master:'
2145 print ' git reparent-branch --root'
2146 print
2147 print 'OR run `git rebase-update` if you think the parent branch is already'
2148 print 'committed.'
2149 print
2150 print ' Current parent: %r' % upstream_branch
2151 return 1
2152
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002153 if not args or cmd == 'land':
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002154 # Default to merging against our best guess of the upstream branch.
2155 args = [cl.GetUpstreamBranch()]
2156
maruel@chromium.org13f623c2011-07-22 16:02:23 +00002157 if options.contributor:
2158 if not re.match('^.*\s<\S+@\S+>$', options.contributor):
2159 print "Please provide contibutor as 'First Last <email@example.com>'"
2160 return 1
2161
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002162 base_branch = args[0]
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002163 base_has_submodules = IsSubmoduleMergeCommit(base_branch)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002164
ukai@chromium.org259e4682012-10-25 07:36:33 +00002165 if is_dirty_git_tree(cmd):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002166 return 1
2167
2168 # This rev-list syntax means "show all commits not in my branch that
2169 # are in base_branch".
2170 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(),
2171 base_branch]).splitlines()
2172 if upstream_commits:
2173 print ('Base branch "%s" has %d commits '
2174 'not in this branch.' % (base_branch, len(upstream_commits)))
2175 print 'Run "git merge %s" before attempting to %s.' % (base_branch, cmd)
2176 return 1
2177
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002178 # This is the revision `svn dcommit` will commit on top of.
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002179 svn_head = None
2180 if cmd == 'dcommit' or base_has_submodules:
2181 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1',
2182 '--pretty=format:%H'])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002183
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002184 if cmd == 'dcommit':
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002185 # If the base_head is a submodule merge commit, the first parent of the
2186 # base_head should be a git-svn commit, which is what we're interested in.
2187 base_svn_head = base_branch
2188 if base_has_submodules:
2189 base_svn_head += '^1'
2190
2191 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002192 if extra_commits:
2193 print ('This branch has %d additional commits not upstreamed yet.'
2194 % len(extra_commits.splitlines()))
2195 print ('Upstream "%s" or rebase this branch on top of the upstream trunk '
2196 'before attempting to %s.' % (base_branch, cmd))
2197 return 1
2198
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002199 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip()
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002200 if not options.bypass_hooks:
maruel@chromium.org13f623c2011-07-22 16:02:23 +00002201 author = None
2202 if options.contributor:
2203 author = re.search(r'\<(.*)\>', options.contributor).group(1)
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002204 hook_results = cl.RunHook(
2205 committing=True,
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002206 may_prompt=not options.force,
2207 verbose=options.verbose,
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002208 change=cl.GetChange(merge_base, author))
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002209 if not hook_results.should_continue():
2210 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002211
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002212 # Check the tree status if the tree status URL is set.
2213 status = GetTreeStatus()
2214 if 'closed' == status:
2215 print('The tree is closed. Please wait for it to reopen. Use '
2216 '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
2217 return 1
2218 elif 'unknown' == status:
2219 print('Unable to determine tree status. Please verify manually and '
2220 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
2221 return 1
maruel@chromium.orgac637152012-01-16 14:19:54 +00002222 else:
2223 breakpad.SendStack(
2224 'GitClHooksBypassedCommit',
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002225 'Issue %s/%s bypassed hook when committing (tree status was "%s")' %
2226 (cl.GetRietveldServer(), cl.GetIssue(), GetTreeStatus()),
maruel@chromium.org2e72bb12012-01-17 15:18:35 +00002227 verbose=False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002228
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002229 change_desc = ChangeDescription(options.message)
2230 if not change_desc.description and cl.GetIssue():
2231 change_desc = ChangeDescription(cl.GetDescription())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002232
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002233 if not change_desc.description:
erg@chromium.org1a173982012-08-29 20:43:05 +00002234 if not cl.GetIssue() and options.bypass_hooks:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002235 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base]))
erg@chromium.org1a173982012-08-29 20:43:05 +00002236 else:
2237 print 'No description set.'
2238 print 'Visit %s/edit to set it.' % (cl.GetIssueURL())
2239 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002240
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002241 # Keep a separate copy for the commit message, because the commit message
2242 # contains the link to the Rietveld issue, while the Rietveld message contains
2243 # the commit viewvc url.
maruel@chromium.orge52678e2013-04-26 18:34:44 +00002244 # Keep a separate copy for the commit message.
2245 if cl.GetIssue():
maruel@chromium.orgcf087782013-07-23 13:08:48 +00002246 change_desc.update_reviewers(cl.GetApprovingReviewers())
maruel@chromium.orge52678e2013-04-26 18:34:44 +00002247
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002248 commit_desc = ChangeDescription(change_desc.description)
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00002249 if cl.GetIssue():
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002250 commit_desc.append_footer('Review URL: %s' % cl.GetIssueURL())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002251 if options.contributor:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002252 commit_desc.append_footer('Patch from %s.' % options.contributor)
2253
agable@chromium.orgeec3ea32013-08-15 20:31:39 +00002254 print('Description:')
2255 print(commit_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002256
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002257 branches = [merge_base, cl.GetBranchRef()]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002258 if not options.force:
iannucci@chromium.org79540052012-10-19 23:15:26 +00002259 print_stats(options.similarity, options.find_copies, branches)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002260
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002261 # We want to squash all this branch's commits into one commit with the proper
2262 # description. We do this by doing a "reset --soft" to the base branch (which
2263 # keeps the working copy the same), then dcommitting that. If origin/master
2264 # has a submodule merge commit, we'll also need to cherry-pick the squashed
2265 # commit onto a branch based on the git-svn head.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002266 MERGE_BRANCH = 'git-cl-commit'
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002267 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
2268 # Delete the branches if they exist.
2269 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]:
2270 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch]
2271 result = RunGitWithCode(showref_cmd)
2272 if result[0] == 0:
2273 RunGit(['branch', '-D', branch])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002274
2275 # We might be in a directory that's present in this branch but not in the
2276 # trunk. Move up to the top of the tree so that git commands that expect a
2277 # valid CWD won't fail after we check out the merge branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002278 rel_base_path = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002279 if rel_base_path:
2280 os.chdir(rel_base_path)
2281
2282 # Stuff our change into the merge branch.
2283 # We wrap in a try...finally block so if anything goes wrong,
2284 # we clean up the branches.
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002285 retcode = -1
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002286 pushed_to_pending = False
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002287 pending_ref = None
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002288 revision = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002289 try:
bauerb@chromium.orgb4a75c42011-03-08 08:35:38 +00002290 RunGit(['checkout', '-q', '-b', MERGE_BRANCH])
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002291 RunGit(['reset', '--soft', merge_base])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002292 if options.contributor:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002293 RunGit(
2294 [
2295 'commit', '--author', options.contributor,
2296 '-m', commit_desc.description,
2297 ])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002298 else:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002299 RunGit(['commit', '-m', commit_desc.description])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002300 if base_has_submodules:
2301 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip()
2302 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head])
2303 RunGit(['checkout', CHERRY_PICK_BRANCH])
2304 RunGit(['cherry-pick', cherry_pick_commit])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002305 if cmd == 'land':
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00002306 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002307 pending_prefix = settings.GetPendingRefPrefix()
2308 if not pending_prefix or branch.startswith(pending_prefix):
2309 # If not using refs/pending/heads/* at all, or target ref is already set
2310 # to pending, then push to the target ref directly.
2311 retcode, output = RunGitWithCode(
2312 ['push', '--porcelain', remote, 'HEAD:%s' % branch])
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002313 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002314 else:
2315 # Cherry-pick the change on top of pending ref and then push it.
2316 assert branch.startswith('refs/'), branch
2317 assert pending_prefix[-1] == '/', pending_prefix
2318 pending_ref = pending_prefix + branch[len('refs/'):]
2319 retcode, output = PushToGitPending(remote, pending_ref, branch)
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002320 pushed_to_pending = (retcode == 0)
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002321 if retcode == 0:
2322 revision = RunGit(['rev-parse', 'HEAD']).strip()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002323 else:
2324 # dcommit the merge branch.
iannucci@chromium.orga1950c42014-12-05 22:15:56 +00002325 cmd_args = [
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00002326 'svn', 'dcommit',
2327 '-C%s' % options.similarity,
2328 '--no-rebase', '--rmdir',
2329 ]
2330 if settings.GetForceHttpsCommitUrl():
2331 # Allow forcing https commit URLs for some projects that don't allow
2332 # committing to http URLs (like Google Code).
2333 remote_url = cl.GetGitSvnRemoteUrl()
2334 if urlparse.urlparse(remote_url).scheme == 'http':
2335 remote_url = remote_url.replace('http://', 'https://')
iannucci@chromium.orga1950c42014-12-05 22:15:56 +00002336 cmd_args.append('--commit-url=%s' % remote_url)
2337 _, output = RunGitWithCode(cmd_args)
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002338 if 'Committed r' in output:
2339 revision = re.match(
2340 '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1)
2341 logging.debug(output)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002342 finally:
2343 # And then swap back to the original branch and clean up.
2344 RunGit(['checkout', '-q', cl.GetBranch()])
2345 RunGit(['branch', '-D', MERGE_BRANCH])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002346 if base_has_submodules:
2347 RunGit(['branch', '-D', CHERRY_PICK_BRANCH])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002348
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002349 if not revision:
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002350 print 'Failed to push. If this persists, please file a bug.'
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002351 return 1
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002352
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002353 killed = False
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002354 if pushed_to_pending:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002355 try:
2356 revision = WaitForRealCommit(remote, revision, base_branch, branch)
2357 # We set pushed_to_pending to False, since it made it all the way to the
2358 # real ref.
2359 pushed_to_pending = False
2360 except KeyboardInterrupt:
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002361 killed = True
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002362
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002363 if cl.GetIssue():
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002364 to_pending = ' to pending queue' if pushed_to_pending else ''
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002365 viewvc_url = settings.GetViewVCUrl()
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002366 if not to_pending:
2367 if viewvc_url and revision:
2368 change_desc.append_footer(
2369 'Committed: %s%s' % (viewvc_url, revision))
2370 elif revision:
2371 change_desc.append_footer('Committed: %s' % (revision,))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002372 print ('Closing issue '
2373 '(you may be prompted for your codereview password)...')
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002374 cl.UpdateDescription(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002375 cl.CloseIssue()
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002376 props = cl.GetIssueProperties()
sadrul@chromium.org34b5d822013-02-18 01:39:24 +00002377 patch_num = len(props['patchsets'])
rmistry@google.com52d224a2014-08-27 14:44:41 +00002378 comment = "Committed patchset #%d (id:%d)%s manually as %s" % (
mark@chromium.org782570c2014-09-26 21:48:02 +00002379 patch_num, props['patchsets'][-1], to_pending, revision)
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002380 if options.bypass_hooks:
2381 comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.'
2382 else:
2383 comment += ' (presubmit successful).'
iannucci@chromium.orgb85a3162013-01-26 01:11:13 +00002384 cl.RpcServer().add_comment(cl.GetIssue(), comment)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002385 cl.SetIssue(None)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002386
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002387 if pushed_to_pending:
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002388 _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
2389 print 'The commit is in the pending queue (%s).' % pending_ref
2390 print (
thakis@chromium.org5f32a962014-09-05 21:33:23 +00002391 'It will show up on %s in ~1 min, once it gets a Cr-Commit-Position '
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002392 'footer.' % branch)
2393
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002394 hook = POSTUPSTREAM_HOOK_PATTERN % cmd
2395 if os.path.isfile(hook):
2396 RunCommand([hook, merge_base], error_ok=True)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002397
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002398 return 1 if killed else 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002399
2400
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002401def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
2402 print
2403 print 'Waiting for commit to be landed on %s...' % real_ref
2404 print '(If you are impatient, you may Ctrl-C once without harm)'
2405 target_tree = RunGit(['rev-parse', '%s:' % pushed_commit]).strip()
2406 current_rev = RunGit(['rev-parse', local_base_ref]).strip()
2407
2408 loop = 0
2409 while True:
2410 sys.stdout.write('fetching (%d)... \r' % loop)
2411 sys.stdout.flush()
2412 loop += 1
2413
2414 RunGit(['retry', 'fetch', remote, real_ref], stderr=subprocess2.VOID)
2415 to_rev = RunGit(['rev-parse', 'FETCH_HEAD']).strip()
2416 commits = RunGit(['rev-list', '%s..%s' % (current_rev, to_rev)])
2417 for commit in commits.splitlines():
2418 if RunGit(['rev-parse', '%s:' % commit]).strip() == target_tree:
2419 print 'Found commit on %s' % real_ref
2420 return commit
2421
2422 current_rev = to_rev
2423
2424
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002425def PushToGitPending(remote, pending_ref, upstream_ref):
2426 """Fetches pending_ref, cherry-picks current HEAD on top of it, pushes.
2427
2428 Returns:
2429 (retcode of last operation, output log of last operation).
2430 """
2431 assert pending_ref.startswith('refs/'), pending_ref
2432 local_pending_ref = 'refs/git-cl/' + pending_ref[len('refs/'):]
2433 cherry = RunGit(['rev-parse', 'HEAD']).strip()
2434 code = 0
2435 out = ''
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002436 max_attempts = 3
2437 attempts_left = max_attempts
2438 while attempts_left:
2439 if attempts_left != max_attempts:
2440 print 'Retrying, %d attempts left...' % (attempts_left - 1,)
2441 attempts_left -= 1
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002442
2443 # Fetch. Retry fetch errors.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002444 print 'Fetching pending ref %s...' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002445 code, out = RunGitWithCode(
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002446 ['retry', 'fetch', remote, '+%s:%s' % (pending_ref, local_pending_ref)])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002447 if code:
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002448 print 'Fetch failed with exit code %d.' % code
2449 if out.strip():
2450 print out.strip()
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002451 continue
2452
2453 # Try to cherry pick. Abort on merge conflicts.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002454 print 'Cherry-picking commit on top of pending ref...'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002455 RunGitWithCode(['checkout', local_pending_ref], suppress_stderr=True)
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002456 code, out = RunGitWithCode(['cherry-pick', cherry])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002457 if code:
2458 print (
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002459 'Your patch doesn\'t apply cleanly to ref \'%s\', '
2460 'the following files have merge conflicts:' % pending_ref)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002461 print RunGit(['diff', '--name-status', '--diff-filter=U']).strip()
2462 print 'Please rebase your patch and try again.'
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002463 RunGitWithCode(['cherry-pick', '--abort'])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002464 return code, out
2465
2466 # Applied cleanly, try to push now. Retry on error (flake or non-ff push).
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002467 print 'Pushing commit to %s... It can take a while.' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002468 code, out = RunGitWithCode(
2469 ['retry', 'push', '--porcelain', remote, 'HEAD:%s' % pending_ref])
2470 if code == 0:
2471 # Success.
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002472 print 'Commit pushed to pending ref successfully!'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002473 return code, out
2474
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002475 print 'Push failed with exit code %d.' % code
2476 if out.strip():
2477 print out.strip()
2478 if IsFatalPushFailure(out):
2479 print (
2480 'Fatal push error. Make sure your .netrc credentials and git '
2481 'user.email are correct and you have push access to the repo.')
2482 return code, out
2483
2484 print 'All attempts to push to pending ref failed.'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002485 return code, out
2486
2487
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002488def IsFatalPushFailure(push_stdout):
2489 """True if retrying push won't help."""
2490 return '(prohibited by Gerrit)' in push_stdout
2491
2492
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002493@subcommand.usage('[upstream branch to apply against]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002494def CMDdcommit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002495 """Commits the current changelist via git-svn."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002496 if not settings.GetIsGitSvn():
thakis@chromium.orgcde3bb62011-01-20 01:16:14 +00002497 message = """This doesn't appear to be an SVN repository.
2498If your project has a git mirror with an upstream SVN master, you probably need
2499to run 'git svn init', see your project's git mirror documentation.
2500If your project has a true writeable upstream repository, you probably want
mark@chromium.org671c7a32014-07-31 17:09:36 +00002501to run 'git cl land' instead.
thakis@chromium.orgcde3bb62011-01-20 01:16:14 +00002502Choose wisely, if you get this wrong, your commit might appear to succeed but
2503will instead be silently ignored."""
2504 print(message)
maruel@chromium.org90541732011-04-01 17:54:18 +00002505 ask_for_data('[Press enter to dcommit or ctrl-C to quit]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002506 return SendUpstream(parser, args, 'dcommit')
2507
2508
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002509@subcommand.usage('[upstream branch to apply against]')
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00002510def CMDland(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002511 """Commits the current changelist via git."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002512 if settings.GetIsGitSvn():
2513 print('This appears to be an SVN repository.')
2514 print('Are you sure you didn\'t mean \'git cl dcommit\'?')
maruel@chromium.org90541732011-04-01 17:54:18 +00002515 ask_for_data('[Press enter to push or ctrl-C to quit]')
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002516 return SendUpstream(parser, args, 'land')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002517
2518
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002519@subcommand.usage('<patch url or issue id>')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002520def CMDpatch(parser, args):
marq@chromium.orge5e59002013-10-02 23:21:25 +00002521 """Patches in a code review."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002522 parser.add_option('-b', dest='newbranch',
2523 help='create a new branch off trunk for the patch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002524 parser.add_option('-f', '--force', action='store_true',
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002525 help='with -b, clobber any existing branch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002526 parser.add_option('-d', '--directory', action='store', metavar='DIR',
2527 help='Change to the directory DIR immediately, '
2528 'before doing anything else.')
2529 parser.add_option('--reject', action='store_true',
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00002530 help='failed patches spew .rej files rather than '
2531 'attempting a 3-way merge')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002532 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit',
2533 help="don't commit after patch applies")
2534 (options, args) = parser.parse_args(args)
2535 if len(args) != 1:
2536 parser.print_help()
2537 return 1
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002538 issue_arg = args[0]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002539
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002540 # We don't want uncommitted changes mixed up with the patch.
2541 if is_dirty_git_tree('patch'):
2542 return 1
2543
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002544 # TODO(maruel): Use apply_issue.py
ukai@chromium.orge8077812012-02-03 03:41:46 +00002545 # TODO(ukai): use gerrit-cherry-pick for gerrit repository?
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002546
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002547 if options.newbranch:
2548 if options.force:
2549 RunGit(['branch', '-D', options.newbranch],
2550 stderr=subprocess2.PIPE, error_ok=True)
2551 RunGit(['checkout', '-b', options.newbranch,
2552 Changelist().GetUpstreamBranch()])
2553
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002554 return PatchIssue(issue_arg, options.reject, options.nocommit,
2555 options.directory)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002556
2557
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002558def PatchIssue(issue_arg, reject, nocommit, directory):
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002559 # There's a "reset --hard" when failing to apply the patch. In order
2560 # not to destroy users' data, make sure the tree is not dirty here.
2561 assert(not is_dirty_git_tree('apply'))
2562
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002563 if type(issue_arg) is int or issue_arg.isdigit():
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002564 # Input is an issue id. Figure out the URL.
maruel@chromium.org52424302012-08-29 15:14:30 +00002565 issue = int(issue_arg)
jochen@chromium.orga26e0472013-07-24 10:25:01 +00002566 cl = Changelist(issue=issue)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002567 patchset = cl.GetMostRecentPatchset()
binji@chromium.org0281f522012-09-14 13:37:59 +00002568 patch_data = cl.GetPatchSetDiff(issue, patchset)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002569 else:
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00002570 # Assume it's a URL to the patch. Default to https.
2571 issue_url = gclient_utils.UpgradeToHttps(issue_arg)
binji@chromium.org0281f522012-09-14 13:37:59 +00002572 match = re.match(r'.*?/issue(\d+)_(\d+).diff', issue_url)
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002573 if not match:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002574 DieWithError('Must pass an issue ID or full URL for '
2575 '\'Download raw patch set\'')
maruel@chromium.org52424302012-08-29 15:14:30 +00002576 issue = int(match.group(1))
binji@chromium.org0281f522012-09-14 13:37:59 +00002577 patchset = int(match.group(2))
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002578 patch_data = urllib2.urlopen(issue_arg).read()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002579
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002580 # Switch up to the top-level directory, if necessary, in preparation for
2581 # applying the patch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002582 top = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002583 if top:
2584 os.chdir(top)
2585
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002586 # Git patches have a/ at the beginning of source paths. We strip that out
2587 # with a sed script rather than the -p flag to patch so we can feed either
2588 # Git or svn-style patches into the same apply command.
2589 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002590 try:
2591 patch_data = subprocess2.check_output(
2592 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
2593 except subprocess2.CalledProcessError:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002594 DieWithError('Git patch mungling failed.')
2595 logging.info(patch_data)
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002596
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002597 # We use "git apply" to apply the patch instead of "patch" so that we can
2598 # pick up file adds.
2599 # The --index flag means: also insert into the index (so we catch adds).
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002600 cmd = ['git', 'apply', '--index', '-p0']
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002601 if directory:
2602 cmd.extend(('--directory', directory))
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002603 if reject:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002604 cmd.append('--reject')
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00002605 elif IsGitVersionAtLeast('1.7.12'):
2606 cmd.append('--3way')
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002607 try:
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002608 subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002609 stdin=patch_data, stdout=subprocess2.VOID)
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002610 except subprocess2.CalledProcessError:
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002611 RunGit(['reset', '--hard'])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002612 DieWithError('Failed to apply the patch')
2613
2614 # If we had an issue, commit the current state and register the issue.
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002615 if not nocommit:
carlosk@chromium.org71284d92014-11-14 18:12:50 +00002616 RunGit(['commit', '-m', ('patch from issue %(i)s at patchset '
2617 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)'
2618 % {'i': issue, 'p': patchset})])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002619 cl = Changelist()
2620 cl.SetIssue(issue)
binji@chromium.org0281f522012-09-14 13:37:59 +00002621 cl.SetPatchset(patchset)
pdr@chromium.org98ca6622013-04-09 20:58:40 +00002622 print "Committed patch locally."
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002623 else:
2624 print "Patch applied to index."
2625 return 0
2626
2627
2628def CMDrebase(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002629 """Rebases current branch on top of svn repo."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002630 # Provide a wrapper for git svn rebase to help avoid accidental
2631 # git svn dcommit.
2632 # It's the only command that doesn't use parser at all since we just defer
2633 # execution to git-svn.
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002634
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002635 return RunGitWithCode(['svn', 'rebase'] + args)[1]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002636
2637
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002638def GetTreeStatus(url=None):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002639 """Fetches the tree status and returns either 'open', 'closed',
2640 'unknown' or 'unset'."""
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002641 url = url or settings.GetTreeStatusUrl(error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002642 if url:
2643 status = urllib2.urlopen(url).read().lower()
2644 if status.find('closed') != -1 or status == '0':
2645 return 'closed'
2646 elif status.find('open') != -1 or status == '1':
2647 return 'open'
2648 return 'unknown'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002649 return 'unset'
2650
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002651
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002652def GetTreeStatusReason():
2653 """Fetches the tree status from a json url and returns the message
2654 with the reason for the tree to be opened or closed."""
msb@chromium.orgbf1a7ba2011-02-01 16:21:46 +00002655 url = settings.GetTreeStatusUrl()
2656 json_url = urlparse.urljoin(url, '/current?format=json')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002657 connection = urllib2.urlopen(json_url)
2658 status = json.loads(connection.read())
2659 connection.close()
2660 return status['message']
2661
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002662
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002663def GetBuilderMaster(bot_list):
2664 """For a given builder, fetch the master from AE if available."""
2665 map_url = 'https://builders-map.appspot.com/'
2666 try:
2667 master_map = json.load(urllib2.urlopen(map_url))
2668 except urllib2.URLError as e:
2669 return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' %
2670 (map_url, e))
2671 except ValueError as e:
2672 return None, ('Invalid json string from %s. Error: %s.' % (map_url, e))
2673 if not master_map:
2674 return None, 'Failed to build master map.'
2675
2676 result_master = ''
2677 for bot in bot_list:
2678 builder = bot.split(':', 1)[0]
2679 master_list = master_map.get(builder, [])
2680 if not master_list:
2681 return None, ('No matching master for builder %s.' % builder)
2682 elif len(master_list) > 1:
2683 return None, ('The builder name %s exists in multiple masters %s.' %
2684 (builder, master_list))
2685 else:
2686 cur_master = master_list[0]
2687 if not result_master:
2688 result_master = cur_master
2689 elif result_master != cur_master:
2690 return None, 'The builders do not belong to the same master.'
2691 return result_master, None
2692
2693
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002694def CMDtree(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002695 """Shows the status of the tree."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002696 _, args = parser.parse_args(args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002697 status = GetTreeStatus()
2698 if 'unset' == status:
2699 print 'You must configure your tree status URL by running "git cl config".'
2700 return 2
2701
2702 print "The tree is %s" % status
2703 print
2704 print GetTreeStatusReason()
2705 if status != 'open':
2706 return 1
2707 return 0
2708
2709
maruel@chromium.org15192402012-09-06 12:38:29 +00002710def CMDtry(parser, args):
2711 """Triggers a try job through Rietveld."""
2712 group = optparse.OptionGroup(parser, "Try job options")
2713 group.add_option(
2714 "-b", "--bot", action="append",
2715 help=("IMPORTANT: specify ONE builder per --bot flag. Use it multiple "
2716 "times to specify multiple builders. ex: "
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00002717 "'-b win_rel -b win_layout'. See "
maruel@chromium.org15192402012-09-06 12:38:29 +00002718 "the try server waterfall for the builders name and the tests "
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00002719 "available."))
maruel@chromium.org15192402012-09-06 12:38:29 +00002720 group.add_option(
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002721 "-m", "--master", default='',
iannucci@chromium.org9e849272014-04-04 00:31:55 +00002722 help=("Specify a try master where to run the tries."))
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002723 group.add_option(
maruel@chromium.org15192402012-09-06 12:38:29 +00002724 "-r", "--revision",
2725 help="Revision to use for the try job; default: the "
2726 "revision will be determined by the try server; see "
2727 "its waterfall for more info")
2728 group.add_option(
2729 "-c", "--clobber", action="store_true", default=False,
2730 help="Force a clobber before building; e.g. don't do an "
2731 "incremental build")
2732 group.add_option(
2733 "--project",
2734 help="Override which project to use. Projects are defined "
2735 "server-side to define what default bot set to use")
2736 group.add_option(
maruel@chromium.org15192402012-09-06 12:38:29 +00002737 "-n", "--name", help="Try job name; default to current branch name")
2738 parser.add_option_group(group)
2739 options, args = parser.parse_args(args)
2740
2741 if args:
2742 parser.error('Unknown arguments: %s' % args)
2743
2744 cl = Changelist()
2745 if not cl.GetIssue():
2746 parser.error('Need to upload first')
2747
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002748 props = cl.GetIssueProperties()
agable@chromium.org787e3062014-08-20 16:31:19 +00002749 if props.get('closed'):
2750 parser.error('Cannot send tryjobs for a closed CL')
2751
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002752 if props.get('private'):
2753 parser.error('Cannot use trybots with private issue')
2754
maruel@chromium.org15192402012-09-06 12:38:29 +00002755 if not options.name:
2756 options.name = cl.GetBranch()
2757
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00002758 if options.bot and not options.master:
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002759 options.master, err_msg = GetBuilderMaster(options.bot)
2760 if err_msg:
2761 parser.error('Tryserver master cannot be found because: %s\n'
2762 'Please manually specify the tryserver master'
2763 ', e.g. "-m tryserver.chromium.linux".' % err_msg)
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00002764
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002765 def GetMasterMap():
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00002766 # Process --bot.
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002767 if not options.bot:
2768 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
maruel@chromium.org15192402012-09-06 12:38:29 +00002769
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002770 # Get try masters from PRESUBMIT.py files.
2771 masters = presubmit_support.DoGetTryMasters(
2772 change,
2773 change.LocalPaths(),
2774 settings.GetRoot(),
2775 None,
2776 None,
2777 options.verbose,
2778 sys.stdout)
2779 if masters:
2780 return masters
stip@chromium.org43064fd2013-12-18 20:07:44 +00002781
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002782 # Fall back to deprecated method: get try slaves from PRESUBMIT.py files.
2783 options.bot = presubmit_support.DoGetTrySlaves(
2784 change,
2785 change.LocalPaths(),
2786 settings.GetRoot(),
2787 None,
2788 None,
2789 options.verbose,
2790 sys.stdout)
2791 if not options.bot:
2792 parser.error('No default try builder to try, use --bot')
maruel@chromium.org15192402012-09-06 12:38:29 +00002793
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002794 builders_and_tests = {}
2795 # TODO(machenbach): The old style command-line options don't support
2796 # multiple try masters yet.
2797 old_style = filter(lambda x: isinstance(x, basestring), options.bot)
2798 new_style = filter(lambda x: isinstance(x, tuple), options.bot)
2799
2800 for bot in old_style:
2801 if ':' in bot:
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00002802 parser.error('Specifying testfilter is no longer supported')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002803 elif ',' in bot:
2804 parser.error('Specify one bot per --bot flag')
2805 else:
2806 builders_and_tests.setdefault(bot, []).append('defaulttests')
2807
2808 for bot, tests in new_style:
2809 builders_and_tests.setdefault(bot, []).extend(tests)
2810
2811 # Return a master map with one master to be backwards compatible. The
2812 # master name defaults to an empty string, which will cause the master
2813 # not to be set on rietveld (deprecated).
2814 return {options.master: builders_and_tests}
2815
2816 masters = GetMasterMap()
stip@chromium.org43064fd2013-12-18 20:07:44 +00002817
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002818 for builders in masters.itervalues():
2819 if any('triggered' in b for b in builders):
2820 print >> sys.stderr, (
2821 'ERROR You are trying to send a job to a triggered bot. This type of'
2822 ' bot requires an\ninitial job from a parent (usually a builder). '
2823 'Instead send your job to the parent.\n'
2824 'Bot list: %s' % builders)
2825 return 1
ilevy@chromium.orgf3b21232012-09-24 20:48:55 +00002826
ilevy@chromium.org36e420b2013-08-06 23:21:12 +00002827 patchset = cl.GetMostRecentPatchset()
2828 if patchset and patchset != cl.GetPatchset():
2829 print(
2830 '\nWARNING Mismatch between local config and server. Did a previous '
2831 'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
2832 'Continuing using\npatchset %s.\n' % patchset)
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002833 try:
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002834 cl.RpcServer().trigger_distributed_try_jobs(
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002835 cl.GetIssue(), patchset, options.name, options.clobber,
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002836 options.revision, masters)
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002837 except urllib2.HTTPError, e:
2838 if e.code == 404:
2839 print('404 from rietveld; '
2840 'did you mean to use "git try" instead of "git cl try"?')
2841 return 1
maruel@chromium.org072d94b2012-09-20 19:20:08 +00002842 print('Tried jobs on:')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002843
2844 for (master, builders) in masters.iteritems():
2845 if master:
2846 print 'Master: %s' % master
2847 length = max(len(builder) for builder in builders)
2848 for builder in sorted(builders):
2849 print ' %*s: %s' % (length, builder, ','.join(builders[builder]))
maruel@chromium.org15192402012-09-06 12:38:29 +00002850 return 0
2851
2852
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002853@subcommand.usage('[new upstream branch]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002854def CMDupstream(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002855 """Prints or sets the name of the upstream branch, if any."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002856 _, args = parser.parse_args(args)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002857 if len(args) > 1:
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002858 parser.error('Unrecognized args: %s' % ' '.join(args))
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002859
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002860 cl = Changelist()
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002861 if args:
2862 # One arg means set upstream branch.
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00002863 branch = cl.GetBranch()
2864 RunGit(['branch', '--set-upstream', branch, args[0]])
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002865 cl = Changelist()
2866 print "Upstream branch set to " + cl.GetUpstreamBranch()
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00002867
2868 # Clear configured merge-base, if there is one.
2869 git_common.remove_merge_base(branch)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002870 else:
2871 print cl.GetUpstreamBranch()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002872 return 0
2873
2874
thestig@chromium.org00858c82013-12-02 23:08:03 +00002875def CMDweb(parser, args):
2876 """Opens the current CL in the web browser."""
2877 _, args = parser.parse_args(args)
2878 if args:
2879 parser.error('Unrecognized args: %s' % ' '.join(args))
2880
2881 issue_url = Changelist().GetIssueURL()
2882 if not issue_url:
2883 print >> sys.stderr, 'ERROR No issue to open'
2884 return 1
2885
2886 webbrowser.open(issue_url)
2887 return 0
2888
2889
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002890def CMDset_commit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002891 """Sets the commit bit to trigger the Commit Queue."""
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002892 _, args = parser.parse_args(args)
2893 if args:
2894 parser.error('Unrecognized args: %s' % ' '.join(args))
2895 cl = Changelist()
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002896 props = cl.GetIssueProperties()
2897 if props.get('private'):
2898 parser.error('Cannot set commit on private issue')
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002899 cl.SetFlag('commit', '1')
2900 return 0
2901
2902
groby@chromium.org411034a2013-02-26 15:12:01 +00002903def CMDset_close(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002904 """Closes the issue."""
groby@chromium.org411034a2013-02-26 15:12:01 +00002905 _, args = parser.parse_args(args)
2906 if args:
2907 parser.error('Unrecognized args: %s' % ' '.join(args))
2908 cl = Changelist()
2909 # Ensure there actually is an issue to close.
2910 cl.GetDescription()
2911 cl.CloseIssue()
2912 return 0
2913
2914
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002915def CMDdiff(parser, args):
wychen@chromium.org37b2ec02015-04-03 00:49:15 +00002916 """Shows differences between local tree and last upload."""
wychen@chromium.org16ca29c2015-04-02 22:26:55 +00002917 parser.parse_args(args)
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002918
2919 # Uncommitted (staged and unstaged) changes will be destroyed by
2920 # "git reset --hard" if there are merging conflicts in PatchIssue().
2921 # Staged changes would be committed along with the patch from last
2922 # upload, hence counted toward the "last upload" side in the final
2923 # diff output, and this is not what we want.
2924 if is_dirty_git_tree('diff'):
2925 return 1
2926
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002927 cl = Changelist()
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002928 issue = cl.GetIssue()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002929 branch = cl.GetBranch()
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002930 if not issue:
2931 DieWithError('No issue found for current branch (%s)' % branch)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002932 TMP_BRANCH = 'git-cl-diff'
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002933 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002934
2935 # Create a new branch based on the merge-base
2936 RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch])
2937 try:
2938 # Patch in the latest changes from rietveld.
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002939 rtn = PatchIssue(issue, False, False, None)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002940 if rtn != 0:
2941 return rtn
2942
wychen@chromium.org06928532015-02-03 02:11:29 +00002943 # Switch back to starting branch and diff against the temporary
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002944 # branch containing the latest rietveld patch.
wychen@chromium.org06928532015-02-03 02:11:29 +00002945 subprocess2.check_call(['git', 'diff', TMP_BRANCH, branch, '--'])
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002946 finally:
2947 RunGit(['checkout', '-q', branch])
2948 RunGit(['branch', '-D', TMP_BRANCH])
2949
2950 return 0
2951
2952
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002953def CMDowners(parser, args):
wychen@chromium.org37b2ec02015-04-03 00:49:15 +00002954 """Interactively find the owners for reviewing."""
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002955 parser.add_option(
2956 '--no-color',
2957 action='store_true',
2958 help='Use this option to disable color output')
2959 options, args = parser.parse_args(args)
2960
2961 author = RunGit(['config', 'user.email']).strip() or None
2962
2963 cl = Changelist()
2964
2965 if args:
2966 if len(args) > 1:
2967 parser.error('Unknown args')
2968 base_branch = args[0]
2969 else:
2970 # Default to diffing against the common ancestor of the upstream branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002971 base_branch = cl.GetCommonAncestorWithUpstream()
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002972
2973 change = cl.GetChange(base_branch, None)
2974 return owners_finder.OwnersFinder(
2975 [f.LocalPath() for f in
2976 cl.GetChange(base_branch, None).AffectedFiles()],
2977 change.RepositoryRoot(), author,
2978 fopen=file, os_path=os.path, glob=glob.glob,
2979 disable_color=options.no_color).run()
2980
2981
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00002982def BuildGitDiffCmd(diff_type, upstream_commit, args, extensions):
2983 """Generates a diff command."""
2984 # Generate diff for the current branch's changes.
2985 diff_cmd = ['diff', '--no-ext-diff', '--no-prefix', diff_type,
2986 upstream_commit, '--' ]
2987
2988 if args:
2989 for arg in args:
2990 if os.path.isdir(arg):
2991 diff_cmd.extend(os.path.join(arg, '*' + ext) for ext in extensions)
2992 elif os.path.isfile(arg):
2993 diff_cmd.append(arg)
2994 else:
2995 DieWithError('Argument "%s" is not a file or a directory' % arg)
2996 else:
2997 diff_cmd.extend('*' + ext for ext in extensions)
2998
2999 return diff_cmd
3000
3001
enne@chromium.org555cfe42014-01-29 18:21:39 +00003002@subcommand.usage('[files or directories to diff]')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003003def CMDformat(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003004 """Runs clang-format on the diff."""
thakis@chromium.org9819b1b2014-12-09 21:21:53 +00003005 CLANG_EXTS = ['.cc', '.cpp', '.h', '.mm', '.proto', '.java']
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003006 parser.add_option('--full', action='store_true',
3007 help='Reformat the full content of all touched files')
3008 parser.add_option('--dry-run', action='store_true',
3009 help='Don\'t modify any file on disk.')
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003010 parser.add_option('--diff', action='store_true',
3011 help='Print diff to stdout rather than modifying files.')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003012 opts, args = parser.parse_args(args)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003013
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00003014 # git diff generates paths against the root of the repository. Change
3015 # to that directory so clang-format can find files even within subdirs.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003016 rel_base_path = settings.GetRelativeRoot()
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00003017 if rel_base_path:
3018 os.chdir(rel_base_path)
3019
digit@chromium.org29e47272013-05-17 17:01:46 +00003020 # Grab the merge-base commit, i.e. the upstream commit of the current
3021 # branch when it was created or the last time it was rebased. This is
3022 # to cover the case where the user may have called "git fetch origin",
3023 # moving the origin branch to a newer commit, but hasn't rebased yet.
3024 upstream_commit = None
3025 cl = Changelist()
3026 upstream_branch = cl.GetUpstreamBranch()
3027 if upstream_branch:
3028 upstream_commit = RunGit(['merge-base', 'HEAD', upstream_branch])
3029 upstream_commit = upstream_commit.strip()
3030
3031 if not upstream_commit:
3032 DieWithError('Could not find base commit for this branch. '
3033 'Are you in detached state?')
3034
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003035 if opts.full:
3036 # Only list the names of modified files.
3037 clang_diff_type = '--name-only'
enne@chromium.org555cfe42014-01-29 18:21:39 +00003038 else:
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003039 # Only generate context-less patches.
3040 clang_diff_type = '-U0'
3041
3042 diff_cmd = BuildGitDiffCmd(clang_diff_type, upstream_commit, args, CLANG_EXTS)
digit@chromium.org29e47272013-05-17 17:01:46 +00003043 diff_output = RunGit(diff_cmd)
3044
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003045 top_dir = os.path.normpath(
3046 RunGit(["rev-parse", "--show-toplevel"]).rstrip('\n'))
3047
3048 # Locate the clang-format binary in the checkout
3049 try:
3050 clang_format_tool = clang_format.FindClangFormatToolInChromiumTree()
3051 except clang_format.NotFoundError, e:
3052 DieWithError(e)
mdempsky@google.comc3b3dc02013-08-05 23:09:49 +00003053
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003054 # Set to 2 to signal to CheckPatchFormatted() that this patch isn't
3055 # formatted. This is used to block during the presubmit.
3056 return_value = 0
3057
digit@chromium.org29e47272013-05-17 17:01:46 +00003058 if opts.full:
3059 # diff_output is a list of files to send to clang-format.
3060 files = diff_output.splitlines()
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003061 if files:
3062 cmd = [clang_format_tool]
3063 if not opts.dry_run and not opts.diff:
3064 cmd.append('-i')
3065 stdout = RunCommand(cmd + files, cwd=top_dir)
3066 if opts.diff:
3067 sys.stdout.write(stdout)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003068 else:
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003069 env = os.environ.copy()
thakis@chromium.orgbd2a9b92014-09-05 18:54:22 +00003070 env['PATH'] = str(os.path.dirname(clang_format_tool))
digit@chromium.org29e47272013-05-17 17:01:46 +00003071 # diff_output is a patch to send to clang-format-diff.py
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003072 try:
3073 script = clang_format.FindClangFormatScriptInChromiumTree(
3074 'clang-format-diff.py')
3075 except clang_format.NotFoundError, e:
3076 DieWithError(e)
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00003077
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003078 cmd = [sys.executable, script, '-p0']
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003079 if not opts.dry_run and not opts.diff:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003080 cmd.append('-i')
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00003081
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003082 stdout = RunCommand(cmd, stdin=diff_output, cwd=top_dir, env=env)
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003083 if opts.diff:
3084 sys.stdout.write(stdout)
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003085 if opts.dry_run and len(stdout) > 0:
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003086 return_value = 2
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003087
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003088 # Build a diff command that only operates on dart files. dart's formatter
3089 # does not have the nice property of only operating on modified chunks, so
3090 # hard code full.
3091 dart_diff_cmd = BuildGitDiffCmd('--name-only', upstream_commit,
3092 args, ['.dart'])
3093 dart_diff_output = RunGit(dart_diff_cmd)
3094 if dart_diff_output:
3095 try:
3096 command = [dart_format.FindDartFmtToolInChromiumTree()]
3097 if not opts.dry_run and not opts.diff:
3098 command.append('-w')
3099 command.extend(dart_diff_output.splitlines())
3100
3101 stdout = RunCommand(command, cwd=top_dir, env=env)
3102 if opts.dry_run and stdout:
3103 return_value = 2
3104 except dart_format.NotFoundError as e:
3105 print ('Unable to check dart code formatting. Dart SDK is not in ' +
3106 'this checkout.')
3107
3108 return return_value
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003109
3110
maruel@chromium.org29404b52014-09-08 22:58:00 +00003111def CMDlol(parser, args):
3112 # This command is intentionally undocumented.
thakis@chromium.org3421c992014-11-02 02:20:32 +00003113 print zlib.decompress(base64.b64decode(
3114 'eNptkLEOwyAMRHe+wupCIqW57v0Vq84WqWtXyrcXnCBsmgMJ+/SSAxMZgRB6NzE'
3115 'E2ObgCKJooYdu4uAQVffUEoE1sRQLxAcqzd7uK2gmStrll1ucV3uZyaY5sXyDd9'
3116 'JAnN+lAXsOMJ90GANAi43mq5/VeeacylKVgi8o6F1SC63FxnagHfJUTfUYdCR/W'
3117 'Ofe+0dHL7PicpytKP750Fh1q2qnLVof4w8OZWNY'))
maruel@chromium.org29404b52014-09-08 22:58:00 +00003118 return 0
3119
3120
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003121class OptionParser(optparse.OptionParser):
3122 """Creates the option parse and add --verbose support."""
3123 def __init__(self, *args, **kwargs):
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003124 optparse.OptionParser.__init__(
3125 self, *args, prog='git cl', version=__version__, **kwargs)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003126 self.add_option(
3127 '-v', '--verbose', action='count', default=0,
3128 help='Use 2 times for more debugging info')
3129
3130 def parse_args(self, args=None, values=None):
3131 options, args = optparse.OptionParser.parse_args(self, args, values)
3132 levels = [logging.WARNING, logging.INFO, logging.DEBUG]
3133 logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)])
3134 return options, args
3135
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003136
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003137def main(argv):
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003138 if sys.hexversion < 0x02060000:
3139 print >> sys.stderr, (
3140 '\nYour python version %s is unsupported, please upgrade.\n' %
3141 sys.version.split(' ', 1)[0])
3142 return 2
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003143
maruel@chromium.orgddd59412011-11-30 14:20:38 +00003144 # Reload settings.
3145 global settings
3146 settings = Settings()
3147
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003148 colorize_CMDstatus_doc()
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003149 dispatcher = subcommand.CommandDispatcher(__name__)
3150 try:
3151 return dispatcher.execute(OptionParser(), argv)
3152 except urllib2.HTTPError, e:
3153 if e.code != 500:
3154 raise
3155 DieWithError(
3156 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
3157 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
sbc@chromium.org013731e2015-02-26 18:28:43 +00003158 return 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003159
3160
3161if __name__ == '__main__':
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003162 # These affect sys.stdout so do it outside of main() to simplify mocks in
3163 # unit testing.
maruel@chromium.org6f09cd92011-04-01 16:38:12 +00003164 fix_encoding.fix_encoding()
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003165 colorama.init()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003166 try:
3167 sys.exit(main(sys.argv[1:]))
3168 except KeyboardInterrupt:
3169 sys.stderr.write('interrupted\n')
3170 sys.exit(1)