blob: 5484713c57cbc601481b388ee6a7380e87952923 [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
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +000037import auth
maruel@chromium.org2a74d372011-03-29 19:05:50 +000038import breakpad # pylint: disable=W0611
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +000039import clang_format
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +000040import dart_format
maruel@chromium.org6f09cd92011-04-01 16:38:12 +000041import fix_encoding
maruel@chromium.org0e0436a2011-10-25 13:32:41 +000042import gclient_utils
iannucci@chromium.org9e849272014-04-04 00:31:55 +000043import git_common
piman@chromium.org336f9122014-09-04 02:16:55 +000044import owners
iannucci@chromium.org9e849272014-04-04 00:31:55 +000045import owners_finder
maruel@chromium.org2a74d372011-03-29 19:05:50 +000046import presubmit_support
maruel@chromium.orgcab38e92011-04-09 00:30:51 +000047import rietveld
maruel@chromium.org2a74d372011-03-29 19:05:50 +000048import scm
maruel@chromium.org0633fb42013-08-16 20:06:14 +000049import subcommand
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000050import subprocess2
maruel@chromium.org2a74d372011-03-29 19:05:50 +000051import watchlists
52
maruel@chromium.org0633fb42013-08-16 20:06:14 +000053__version__ = '1.0'
maruel@chromium.org2a74d372011-03-29 19:05:50 +000054
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +000055DEFAULT_SERVER = 'https://codereview.appspot.com'
maruel@chromium.org0ba7f962011-01-11 22:13:58 +000056POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000057DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +000058GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit'
sivachandra@chromium.orgaebe87f2012-10-22 20:34:21 +000059CHANGE_ID = 'Change-Id:'
rmistry@google.comc68112d2015-03-03 12:48:06 +000060REFS_THAT_ALIAS_TO_OTHER_REFS = {
61 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master',
62 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master',
63}
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000064
thestig@chromium.org44202a22014-03-11 19:22:18 +000065# Valid extensions for files we want to lint.
66DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
67DEFAULT_LINT_IGNORE_REGEX = r"$^"
68
maruel@chromium.org2e23ce32013-05-07 12:42:28 +000069# Shortcut since it quickly becomes redundant.
70Fore = colorama.Fore
maruel@chromium.org90541732011-04-01 17:54:18 +000071
maruel@chromium.orgddd59412011-11-30 14:20:38 +000072# Initialized in main()
73settings = None
74
75
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000076def DieWithError(message):
dpranke@chromium.org970c5222011-03-12 00:32:24 +000077 print >> sys.stderr, message
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000078 sys.exit(1)
79
80
thestig@chromium.org8b0553c2014-02-11 00:33:37 +000081def GetNoGitPagerEnv():
82 env = os.environ.copy()
83 # 'cat' is a magical git string that disables pagers on all platforms.
84 env['GIT_PAGER'] = 'cat'
85 return env
86
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +000087
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000088def RunCommand(args, error_ok=False, error_message=None, **kwargs):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000089 try:
maruel@chromium.org373af802012-05-25 21:07:33 +000090 return subprocess2.check_output(args, shell=False, **kwargs)
maruel@chromium.org78936cb2013-04-11 00:17:52 +000091 except subprocess2.CalledProcessError as e:
92 logging.debug('Failed running %s', args)
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000093 if not error_ok:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000094 DieWithError(
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000095 'Command "%s" failed.\n%s' % (
96 ' '.join(args), error_message or e.stdout or ''))
97 return e.stdout
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000098
99
100def RunGit(args, **kwargs):
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000101 """Returns stdout."""
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000102 return RunCommand(['git'] + args, **kwargs)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000103
104
enne@chromium.org3b7e15c2014-01-21 17:44:47 +0000105def RunGitWithCode(args, suppress_stderr=False):
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000106 """Returns return code and stdout."""
szager@chromium.org9bb85e22012-06-13 20:28:23 +0000107 try:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +0000108 if suppress_stderr:
109 stderr = subprocess2.VOID
110 else:
111 stderr = sys.stderr
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000112 out, code = subprocess2.communicate(['git'] + args,
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000113 env=GetNoGitPagerEnv(),
enne@chromium.org3b7e15c2014-01-21 17:44:47 +0000114 stdout=subprocess2.PIPE,
115 stderr=stderr)
szager@chromium.org9bb85e22012-06-13 20:28:23 +0000116 return code, out[0]
117 except ValueError:
118 # When the subprocess fails, it returns None. That triggers a ValueError
119 # when trying to unpack the return value into (out, code).
120 return 1, ''
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000121
122
bauerb@chromium.org27386dd2015-02-16 10:45:39 +0000123def RunGitSilent(args):
124 """Returns stdout, suppresses stderr and ingores the return code."""
125 return RunGitWithCode(args, suppress_stderr=True)[1]
126
127
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000128def IsGitVersionAtLeast(min_version):
ilevy@chromium.orgcc56ee42013-07-10 22:16:29 +0000129 prefix = 'git version '
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000130 version = RunGit(['--version']).strip()
ilevy@chromium.orgcc56ee42013-07-10 22:16:29 +0000131 return (version.startswith(prefix) and
132 LooseVersion(version[len(prefix):]) >= LooseVersion(min_version))
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000133
134
maruel@chromium.org90541732011-04-01 17:54:18 +0000135def ask_for_data(prompt):
136 try:
137 return raw_input(prompt)
138 except KeyboardInterrupt:
139 # Hide the exception.
140 sys.exit(1)
141
142
iannucci@chromium.org79540052012-10-19 23:15:26 +0000143def git_set_branch_value(key, value):
144 branch = Changelist().GetBranch()
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +0000145 if not branch:
146 return
147
148 cmd = ['config']
149 if isinstance(value, int):
150 cmd.append('--int')
151 git_key = 'branch.%s.%s' % (branch, key)
152 RunGit(cmd + [git_key, str(value)])
iannucci@chromium.org79540052012-10-19 23:15:26 +0000153
154
155def git_get_branch_default(key, default):
156 branch = Changelist().GetBranch()
157 if branch:
158 git_key = 'branch.%s.%s' % (branch, key)
159 (_, stdout) = RunGitWithCode(['config', '--int', '--get', git_key])
160 try:
161 return int(stdout.strip())
162 except ValueError:
163 pass
164 return default
165
166
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000167def add_git_similarity(parser):
168 parser.add_option(
iannucci@chromium.org79540052012-10-19 23:15:26 +0000169 '--similarity', metavar='SIM', type='int', action='store',
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000170 help='Sets the percentage that a pair of files need to match in order to'
171 ' be considered copies (default 50)')
iannucci@chromium.org79540052012-10-19 23:15:26 +0000172 parser.add_option(
173 '--find-copies', action='store_true',
174 help='Allows git to look for copies.')
175 parser.add_option(
176 '--no-find-copies', action='store_false', dest='find_copies',
177 help='Disallows git from looking for copies.')
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000178
179 old_parser_args = parser.parse_args
180 def Parse(args):
181 options, args = old_parser_args(args)
182
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000183 if options.similarity is None:
iannucci@chromium.org79540052012-10-19 23:15:26 +0000184 options.similarity = git_get_branch_default('git-cl-similarity', 50)
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000185 else:
iannucci@chromium.org79540052012-10-19 23:15:26 +0000186 print('Note: Saving similarity of %d%% in git config.'
187 % options.similarity)
188 git_set_branch_value('git-cl-similarity', options.similarity)
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000189
iannucci@chromium.org79540052012-10-19 23:15:26 +0000190 options.similarity = max(0, min(options.similarity, 100))
191
192 if options.find_copies is None:
193 options.find_copies = bool(
194 git_get_branch_default('git-find-copies', True))
195 else:
196 git_set_branch_value('git-find-copies', int(options.find_copies))
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000197
198 print('Using %d%% similarity for rename/copy detection. '
199 'Override with --similarity.' % options.similarity)
200
201 return options, args
202 parser.parse_args = Parse
203
204
bauerb@chromium.org866276c2011-03-18 20:09:31 +0000205def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
206 """Return the corresponding git ref if |base_url| together with |glob_spec|
207 matches the full |url|.
208
209 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below).
210 """
211 fetch_suburl, as_ref = glob_spec.split(':')
212 if allow_wildcards:
213 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl)
214 if glob_match:
215 # Parse specs like "branches/*/src:refs/remotes/svn/*" or
216 # "branches/{472,597,648}/src:refs/remotes/svn/*".
217 branch_re = re.escape(base_url)
218 if glob_match.group(1):
219 branch_re += '/' + re.escape(glob_match.group(1))
220 wildcard = glob_match.group(2)
221 if wildcard == '*':
222 branch_re += '([^/]*)'
223 else:
224 # Escape and replace surrounding braces with parentheses and commas
225 # with pipe symbols.
226 wildcard = re.escape(wildcard)
227 wildcard = re.sub('^\\\\{', '(', wildcard)
228 wildcard = re.sub('\\\\,', '|', wildcard)
229 wildcard = re.sub('\\\\}$', ')', wildcard)
230 branch_re += wildcard
231 if glob_match.group(3):
232 branch_re += re.escape(glob_match.group(3))
233 match = re.match(branch_re, url)
234 if match:
235 return re.sub('\*$', match.group(1), as_ref)
236
237 # Parse specs like "trunk/src:refs/remotes/origin/trunk".
238 if fetch_suburl:
239 full_url = base_url + '/' + fetch_suburl
240 else:
241 full_url = base_url
242 if full_url == url:
243 return as_ref
244 return None
245
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000246
iannucci@chromium.org79540052012-10-19 23:15:26 +0000247def print_stats(similarity, find_copies, args):
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000248 """Prints statistics about the change to the user."""
249 # --no-ext-diff is broken in some versions of Git, so try to work around
250 # this by overriding the environment (but there is still a problem if the
251 # git config key "diff.external" is used).
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000252 env = GetNoGitPagerEnv()
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000253 if 'GIT_EXTERNAL_DIFF' in env:
254 del env['GIT_EXTERNAL_DIFF']
iannucci@chromium.org79540052012-10-19 23:15:26 +0000255
256 if find_copies:
257 similarity_options = ['--find-copies-harder', '-l100000',
258 '-C%s' % similarity]
259 else:
260 similarity_options = ['-M%s' % similarity]
261
szager@chromium.orgd057f9a2014-05-29 21:09:36 +0000262 try:
263 stdout = sys.stdout.fileno()
264 except AttributeError:
265 stdout = None
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000266 return subprocess2.call(
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000267 ['git',
bratell@opera.comf267b0e2013-05-02 09:11:43 +0000268 'diff', '--no-ext-diff', '--stat'] + similarity_options + args,
szager@chromium.orgd057f9a2014-05-29 21:09:36 +0000269 stdout=stdout, env=env)
maruel@chromium.org49e3d802012-07-18 23:54:45 +0000270
271
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000272class Settings(object):
273 def __init__(self):
274 self.default_server = None
275 self.cc = None
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000276 self.root = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000277 self.is_git_svn = None
278 self.svn_branch = None
279 self.tree_status_url = None
280 self.viewvc_url = None
281 self.updated = False
ukai@chromium.orge8077812012-02-03 03:41:46 +0000282 self.is_gerrit = None
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000283 self.git_editor = None
sheyang@chromium.org152cf832014-06-11 21:37:49 +0000284 self.project = None
kjellander@chromium.org6abc6522014-12-02 07:34:49 +0000285 self.force_https_commit_url = None
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000286 self.pending_ref_prefix = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000287
288 def LazyUpdateIfNeeded(self):
289 """Updates the settings from a codereview.settings file, if available."""
290 if not self.updated:
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000291 # The only value that actually changes the behavior is
292 # autoupdate = "false". Everything else means "true".
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000293 autoupdate = RunGit(['config', 'rietveld.autoupdate'],
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000294 error_ok=True
295 ).strip().lower()
296
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000297 cr_settings_file = FindCodereviewSettingsFile()
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000298 if autoupdate != 'false' and cr_settings_file:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000299 LoadCodereviewSettingsFromFile(cr_settings_file)
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000300 # set updated to True to avoid infinite calling loop
301 # through DownloadHooks
ukai@chromium.org78c4b982012-02-14 02:20:26 +0000302 self.updated = True
303 DownloadHooks(False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000304 self.updated = True
305
306 def GetDefaultServerUrl(self, error_ok=False):
307 if not self.default_server:
308 self.LazyUpdateIfNeeded()
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000309 self.default_server = gclient_utils.UpgradeToHttps(
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000310 self._GetRietveldConfig('server', error_ok=True))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000311 if error_ok:
312 return self.default_server
313 if not self.default_server:
314 error_message = ('Could not find settings file. You must configure '
315 'your review setup by running "git cl config".')
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000316 self.default_server = gclient_utils.UpgradeToHttps(
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000317 self._GetRietveldConfig('server', error_message=error_message))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000318 return self.default_server
319
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000320 @staticmethod
321 def GetRelativeRoot():
322 return RunGit(['rev-parse', '--show-cdup']).strip()
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000323
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000324 def GetRoot(self):
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000325 if self.root is None:
326 self.root = os.path.abspath(self.GetRelativeRoot())
327 return self.root
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000328
329 def GetIsGitSvn(self):
330 """Return true if this repo looks like it's using git-svn."""
331 if self.is_git_svn is None:
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000332 if self.GetPendingRefPrefix():
333 # If PENDING_REF_PREFIX is set then it's a pure git repo no matter what.
334 self.is_git_svn = False
335 else:
336 # If you have any "svn-remote.*" config keys, we think you're using svn.
337 self.is_git_svn = RunGitWithCode(
338 ['config', '--local', '--get-regexp', r'^svn-remote\.'])[0] == 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000339 return self.is_git_svn
340
341 def GetSVNBranch(self):
342 if self.svn_branch is None:
343 if not self.GetIsGitSvn():
344 DieWithError('Repo doesn\'t appear to be a git-svn repo.')
345
346 # Try to figure out which remote branch we're based on.
347 # Strategy:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000348 # 1) iterate through our branch history and find the svn URL.
349 # 2) find the svn-remote that fetches from the URL.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000350
351 # regexp matching the git-svn line that contains the URL.
352 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
353
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000354 # We don't want to go through all of history, so read a line from the
355 # pipe at a time.
356 # The -100 is an arbitrary limit so we don't search forever.
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000357 cmd = ['git', 'log', '-100', '--pretty=medium']
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000358 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE,
359 env=GetNoGitPagerEnv())
maruel@chromium.org740f9d72011-06-10 18:33:10 +0000360 url = None
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000361 for line in proc.stdout:
362 match = git_svn_re.match(line)
363 if match:
364 url = match.group(1)
365 proc.stdout.close() # Cut pipe.
366 break
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000367
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000368 if url:
369 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$')
370 remotes = RunGit(['config', '--get-regexp',
371 r'^svn-remote\..*\.url']).splitlines()
372 for remote in remotes:
373 match = svn_remote_re.match(remote)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000374 if match:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000375 remote = match.group(1)
376 base_url = match.group(2)
szager@chromium.org4ac25532013-12-16 22:07:02 +0000377 rewrite_root = RunGit(
378 ['config', 'svn-remote.%s.rewriteRoot' % remote],
379 error_ok=True).strip()
380 if rewrite_root:
381 base_url = rewrite_root
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000382 fetch_spec = RunGit(
bauerb@chromium.org866276c2011-03-18 20:09:31 +0000383 ['config', 'svn-remote.%s.fetch' % remote],
384 error_ok=True).strip()
385 if fetch_spec:
386 self.svn_branch = MatchSvnGlob(url, base_url, fetch_spec, False)
387 if self.svn_branch:
388 break
389 branch_spec = RunGit(
390 ['config', 'svn-remote.%s.branches' % remote],
391 error_ok=True).strip()
392 if branch_spec:
393 self.svn_branch = MatchSvnGlob(url, base_url, branch_spec, True)
394 if self.svn_branch:
395 break
396 tag_spec = RunGit(
397 ['config', 'svn-remote.%s.tags' % remote],
398 error_ok=True).strip()
399 if tag_spec:
400 self.svn_branch = MatchSvnGlob(url, base_url, tag_spec, True)
401 if self.svn_branch:
402 break
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000403
404 if not self.svn_branch:
405 DieWithError('Can\'t guess svn branch -- try specifying it on the '
406 'command line')
407
408 return self.svn_branch
409
410 def GetTreeStatusUrl(self, error_ok=False):
411 if not self.tree_status_url:
412 error_message = ('You must configure your tree status URL by running '
413 '"git cl config".')
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000414 self.tree_status_url = self._GetRietveldConfig(
415 'tree-status-url', error_ok=error_ok, error_message=error_message)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000416 return self.tree_status_url
417
418 def GetViewVCUrl(self):
419 if not self.viewvc_url:
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000420 self.viewvc_url = self._GetRietveldConfig('viewvc-url', error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000421 return self.viewvc_url
422
rmistry@google.com90752582014-01-14 21:04:50 +0000423 def GetBugPrefix(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000424 return self._GetRietveldConfig('bug-prefix', error_ok=True)
rmistry@google.com90752582014-01-14 21:04:50 +0000425
rmistry@google.com5626a922015-02-26 14:03:30 +0000426 def GetRunPostUploadHook(self):
427 run_post_upload_hook = self._GetRietveldConfig(
428 'run-post-upload-hook', error_ok=True)
429 return run_post_upload_hook == "True"
430
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000431 def GetDefaultCCList(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000432 return self._GetRietveldConfig('cc', error_ok=True)
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000433
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +0000434 def GetDefaultPrivateFlag(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000435 return self._GetRietveldConfig('private', error_ok=True)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +0000436
ukai@chromium.orge8077812012-02-03 03:41:46 +0000437 def GetIsGerrit(self):
438 """Return true if this repo is assosiated with gerrit code review system."""
439 if self.is_gerrit is None:
440 self.is_gerrit = self._GetConfig('gerrit.host', error_ok=True)
441 return self.is_gerrit
442
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000443 def GetGitEditor(self):
444 """Return the editor specified in the git config, or None if none is."""
445 if self.git_editor is None:
446 self.git_editor = self._GetConfig('core.editor', error_ok=True)
447 return self.git_editor or None
448
thestig@chromium.org44202a22014-03-11 19:22:18 +0000449 def GetLintRegex(self):
450 return (self._GetRietveldConfig('cpplint-regex', error_ok=True) or
451 DEFAULT_LINT_REGEX)
452
453 def GetLintIgnoreRegex(self):
454 return (self._GetRietveldConfig('cpplint-ignore-regex', error_ok=True) or
455 DEFAULT_LINT_IGNORE_REGEX)
456
sheyang@chromium.org152cf832014-06-11 21:37:49 +0000457 def GetProject(self):
458 if not self.project:
459 self.project = self._GetRietveldConfig('project', error_ok=True)
460 return self.project
461
kjellander@chromium.org6abc6522014-12-02 07:34:49 +0000462 def GetForceHttpsCommitUrl(self):
463 if not self.force_https_commit_url:
464 self.force_https_commit_url = self._GetRietveldConfig(
465 'force-https-commit-url', error_ok=True)
466 return self.force_https_commit_url
467
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000468 def GetPendingRefPrefix(self):
469 if not self.pending_ref_prefix:
470 self.pending_ref_prefix = self._GetRietveldConfig(
471 'pending-ref-prefix', error_ok=True)
472 return self.pending_ref_prefix
473
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000474 def _GetRietveldConfig(self, param, **kwargs):
475 return self._GetConfig('rietveld.' + param, **kwargs)
476
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000477 def _GetConfig(self, param, **kwargs):
478 self.LazyUpdateIfNeeded()
479 return RunGit(['config', param], **kwargs).strip()
480
481
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000482def ShortBranchName(branch):
483 """Convert a name like 'refs/heads/foo' to just 'foo'."""
484 return branch.replace('refs/heads/', '')
485
486
487class Changelist(object):
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +0000488 def __init__(self, branchref=None, issue=None, auth_config=None):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000489 # Poke settings so we get the "configure your server" message if necessary.
maruel@chromium.org379d07a2011-11-30 14:58:10 +0000490 global settings
491 if not settings:
492 # Happens when git_cl.py is used as a utility library.
493 settings = Settings()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000494 settings.GetDefaultServerUrl()
495 self.branchref = branchref
496 if self.branchref:
497 self.branch = ShortBranchName(self.branchref)
498 else:
499 self.branch = None
500 self.rietveld_server = None
501 self.upstream_branch = None
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000502 self.lookedup_issue = False
503 self.issue = issue or None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000504 self.has_description = False
505 self.description = None
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000506 self.lookedup_patchset = False
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000507 self.patchset = None
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000508 self.cc = None
509 self.watchers = ()
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +0000510 self._auth_config = auth_config
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000511 self._props = None
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +0000512 self._remote = None
513 self._rpc_server = None
514
515 @property
516 def auth_config(self):
517 return self._auth_config
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000518
519 def GetCCList(self):
520 """Return the users cc'd on this CL.
521
522 Return is a string suitable for passing to gcl with the --cc flag.
523 """
524 if self.cc is None:
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +0000525 base_cc = settings.GetDefaultCCList()
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000526 more_cc = ','.join(self.watchers)
527 self.cc = ','.join(filter(None, (base_cc, more_cc))) or ''
528 return self.cc
529
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +0000530 def GetCCListWithoutDefault(self):
531 """Return the users cc'd on this CL excluding default ones."""
532 if self.cc is None:
533 self.cc = ','.join(self.watchers)
534 return self.cc
535
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000536 def SetWatchers(self, watchers):
537 """Set the list of email addresses that should be cc'd based on the changed
538 files in this CL.
539 """
540 self.watchers = watchers
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000541
542 def GetBranch(self):
543 """Returns the short branch name, e.g. 'master'."""
544 if not self.branch:
szager@chromium.orgd62c61f2014-10-20 22:33:21 +0000545 branchref = RunGit(['symbolic-ref', 'HEAD'],
546 stderr=subprocess2.VOID, error_ok=True).strip()
547 if not branchref:
548 return None
549 self.branchref = branchref
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000550 self.branch = ShortBranchName(self.branchref)
551 return self.branch
552
553 def GetBranchRef(self):
554 """Returns the full branch name, e.g. 'refs/heads/master'."""
555 self.GetBranch() # Poke the lazy loader.
556 return self.branchref
557
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000558 @staticmethod
559 def FetchUpstreamTuple(branch):
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +0000560 """Returns a tuple containing remote and remote ref,
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000561 e.g. 'origin', 'refs/heads/master'
562 """
563 remote = '.'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000564 upstream_branch = RunGit(['config', 'branch.%s.merge' % branch],
565 error_ok=True).strip()
566 if upstream_branch:
567 remote = RunGit(['config', 'branch.%s.remote' % branch]).strip()
568 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000569 upstream_branch = RunGit(['config', 'rietveld.upstream-branch'],
570 error_ok=True).strip()
571 if upstream_branch:
572 remote = RunGit(['config', 'rietveld.upstream-remote']).strip()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000573 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000574 # Fall back on trying a git-svn upstream branch.
575 if settings.GetIsGitSvn():
576 upstream_branch = settings.GetSVNBranch()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000577 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000578 # Else, try to guess the origin remote.
579 remote_branches = RunGit(['branch', '-r']).split()
580 if 'origin/master' in remote_branches:
581 # Fall back on origin/master if it exits.
582 remote = 'origin'
583 upstream_branch = 'refs/heads/master'
584 elif 'origin/trunk' in remote_branches:
585 # Fall back on origin/trunk if it exists. Generally a shared
586 # git-svn clone
587 remote = 'origin'
588 upstream_branch = 'refs/heads/trunk'
589 else:
590 DieWithError("""Unable to determine default branch to diff against.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000591Either pass complete "git diff"-style arguments, like
592 git cl upload origin/master
593or verify this branch is set up to track another (via the --track argument to
594"git checkout -b ...").""")
595
596 return remote, upstream_branch
597
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000598 def GetCommonAncestorWithUpstream(self):
iannucci@chromium.org9e849272014-04-04 00:31:55 +0000599 return git_common.get_or_create_merge_base(self.GetBranch(),
600 self.GetUpstreamBranch())
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000601
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000602 def GetUpstreamBranch(self):
603 if self.upstream_branch is None:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000604 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000605 if remote is not '.':
mmoss@chromium.orge7585452014-08-24 01:41:11 +0000606 upstream_branch = upstream_branch.replace('refs/heads/',
607 'refs/remotes/%s/' % remote)
608 upstream_branch = upstream_branch.replace('refs/branch-heads/',
609 'refs/remotes/branch-heads/')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000610 self.upstream_branch = upstream_branch
611 return self.upstream_branch
612
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000613 def GetRemoteBranch(self):
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000614 if not self._remote:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000615 remote, branch = None, self.GetBranch()
616 seen_branches = set()
617 while branch not in seen_branches:
618 seen_branches.add(branch)
619 remote, branch = self.FetchUpstreamTuple(branch)
620 branch = ShortBranchName(branch)
621 if remote != '.' or branch.startswith('refs/remotes'):
622 break
623 else:
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000624 remotes = RunGit(['remote'], error_ok=True).split()
625 if len(remotes) == 1:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000626 remote, = remotes
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000627 elif 'origin' in remotes:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000628 remote = 'origin'
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000629 logging.warning('Could not determine which remote this change is '
630 'associated with, so defaulting to "%s". This may '
631 'not be what you want. You may prevent this message '
632 'by running "git svn info" as documented here: %s',
633 self._remote,
634 GIT_INSTRUCTIONS_URL)
635 else:
636 logging.warn('Could not determine which remote this change is '
637 'associated with. You may prevent this message by '
638 'running "git svn info" as documented here: %s',
639 GIT_INSTRUCTIONS_URL)
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000640 branch = 'HEAD'
641 if branch.startswith('refs/remotes'):
642 self._remote = (remote, branch)
mmoss@chromium.orge7585452014-08-24 01:41:11 +0000643 elif branch.startswith('refs/branch-heads/'):
644 self._remote = (remote, branch.replace('refs/', 'refs/remotes/'))
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000645 else:
646 self._remote = (remote, 'refs/remotes/%s/%s' % (remote, branch))
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000647 return self._remote
648
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000649 def GitSanityChecks(self, upstream_git_obj):
650 """Checks git repo status and ensures diff is from local commits."""
651
sbc@chromium.org79706062015-01-14 21:18:12 +0000652 if upstream_git_obj is None:
653 if self.GetBranch() is None:
654 print >> sys.stderr, (
655 'ERROR: unable to dertermine current branch (detached HEAD?)')
656 else:
657 print >> sys.stderr, (
658 'ERROR: no upstream branch')
659 return False
660
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000661 # Verify the commit we're diffing against is in our current branch.
662 upstream_sha = RunGit(['rev-parse', '--verify', upstream_git_obj]).strip()
663 common_ancestor = RunGit(['merge-base', upstream_sha, 'HEAD']).strip()
664 if upstream_sha != common_ancestor:
665 print >> sys.stderr, (
666 'ERROR: %s is not in the current branch. You may need to rebase '
667 'your tracking branch' % upstream_sha)
668 return False
669
670 # List the commits inside the diff, and verify they are all local.
671 commits_in_diff = RunGit(
672 ['rev-list', '^%s' % upstream_sha, 'HEAD']).splitlines()
673 code, remote_branch = RunGitWithCode(['config', 'gitcl.remotebranch'])
674 remote_branch = remote_branch.strip()
675 if code != 0:
676 _, remote_branch = self.GetRemoteBranch()
677
678 commits_in_remote = RunGit(
679 ['rev-list', '^%s' % upstream_sha, remote_branch]).splitlines()
680
681 common_commits = set(commits_in_diff) & set(commits_in_remote)
682 if common_commits:
683 print >> sys.stderr, (
684 'ERROR: Your diff contains %d commits already in %s.\n'
685 'Run "git log --oneline %s..HEAD" to get a list of commits in '
686 'the diff. If you are using a custom git flow, you can override'
687 ' the reference used for this check with "git config '
688 'gitcl.remotebranch <git-ref>".' % (
689 len(common_commits), remote_branch, upstream_git_obj))
690 return False
691 return True
692
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +0000693 def GetGitBaseUrlFromConfig(self):
sheyang@chromium.orga656e702014-05-15 20:43:05 +0000694 """Return the configured base URL from branch.<branchname>.baseurl.
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +0000695
696 Returns None if it is not set.
697 """
sheyang@chromium.orga656e702014-05-15 20:43:05 +0000698 return RunGit(['config', 'branch.%s.base-url' % self.GetBranch()],
699 error_ok=True).strip()
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000700
kjellander@chromium.org6abc6522014-12-02 07:34:49 +0000701 def GetGitSvnRemoteUrl(self):
702 """Return the configured git-svn remote URL parsed from git svn info.
703
704 Returns None if it is not set.
705 """
706 # URL is dependent on the current directory.
707 data = RunGit(['svn', 'info'], cwd=settings.GetRoot())
708 if data:
709 keys = dict(line.split(': ', 1) for line in data.splitlines()
710 if ': ' in line)
711 return keys.get('URL', None)
712 return None
713
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000714 def GetRemoteUrl(self):
715 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'.
716
717 Returns None if there is no remote.
718 """
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000719 remote, _ = self.GetRemoteBranch()
dyen@chromium.org2a13d4f2014-06-13 00:06:37 +0000720 url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip()
721
722 # If URL is pointing to a local directory, it is probably a git cache.
723 if os.path.isdir(url):
724 url = RunGit(['config', 'remote.%s.url' % remote],
725 error_ok=True,
726 cwd=url).strip()
727 return url
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000728
729 def GetIssue(self):
maruel@chromium.org52424302012-08-29 15:14:30 +0000730 """Returns the issue number as a int or None if not set."""
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000731 if self.issue is None and not self.lookedup_issue:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000732 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip()
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000733 self.issue = int(issue) or None if issue else None
734 self.lookedup_issue = True
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000735 return self.issue
736
737 def GetRietveldServer(self):
evan@chromium.org0af9b702012-02-11 00:42:16 +0000738 if not self.rietveld_server:
739 # If we're on a branch then get the server potentially associated
740 # with that branch.
741 if self.GetIssue():
szager@chromium.orgd62c61f2014-10-20 22:33:21 +0000742 rietveld_server_config = self._RietveldServer()
743 if rietveld_server_config:
744 self.rietveld_server = gclient_utils.UpgradeToHttps(RunGit(
745 ['config', rietveld_server_config], error_ok=True).strip())
evan@chromium.org0af9b702012-02-11 00:42:16 +0000746 if not self.rietveld_server:
747 self.rietveld_server = settings.GetDefaultServerUrl()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000748 return self.rietveld_server
749
750 def GetIssueURL(self):
751 """Get the URL for a particular issue."""
dbeam@chromium.org015fd3d2013-06-18 19:02:50 +0000752 if not self.GetIssue():
753 return None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000754 return '%s/%s' % (self.GetRietveldServer(), self.GetIssue())
755
756 def GetDescription(self, pretty=False):
757 if not self.has_description:
758 if self.GetIssue():
maruel@chromium.org52424302012-08-29 15:14:30 +0000759 issue = self.GetIssue()
miket@chromium.org183df1a2012-01-04 19:44:55 +0000760 try:
761 self.description = self.RpcServer().get_description(issue).strip()
maruel@chromium.org85616e02014-07-28 15:37:55 +0000762 except urllib2.HTTPError as e:
miket@chromium.org183df1a2012-01-04 19:44:55 +0000763 if e.code == 404:
764 DieWithError(
765 ('\nWhile fetching the description for issue %d, received a '
766 '404 (not found)\n'
767 'error. It is likely that you deleted this '
768 'issue on the server. If this is the\n'
769 'case, please run\n\n'
770 ' git cl issue 0\n\n'
771 'to clear the association with the deleted issue. Then run '
772 'this command again.') % issue)
773 else:
774 DieWithError(
yujie.mao@intel.comdaee1d32013-12-18 11:55:03 +0000775 '\nFailed to fetch issue description. HTTP error %d' % e.code)
maruel@chromium.org85616e02014-07-28 15:37:55 +0000776 except urllib2.URLError as e:
777 print >> sys.stderr, (
778 'Warning: Failed to retrieve CL description due to network '
779 'failure.')
780 self.description = ''
781
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000782 self.has_description = True
783 if pretty:
784 wrapper = textwrap.TextWrapper()
785 wrapper.initial_indent = wrapper.subsequent_indent = ' '
786 return wrapper.fill(self.description)
787 return self.description
788
789 def GetPatchset(self):
maruel@chromium.org52424302012-08-29 15:14:30 +0000790 """Returns the patchset number as a int or None if not set."""
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000791 if self.patchset is None and not self.lookedup_patchset:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000792 patchset = RunGit(['config', self._PatchsetSetting()],
793 error_ok=True).strip()
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000794 self.patchset = int(patchset) or None if patchset else None
795 self.lookedup_patchset = True
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000796 return self.patchset
797
798 def SetPatchset(self, patchset):
799 """Set this branch's patchset. If patchset=0, clears the patchset."""
800 if patchset:
801 RunGit(['config', self._PatchsetSetting(), str(patchset)])
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000802 self.patchset = patchset
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000803 else:
804 RunGit(['config', '--unset', self._PatchsetSetting()],
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000805 stderr=subprocess2.PIPE, error_ok=True)
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000806 self.patchset = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000807
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000808 def GetMostRecentPatchset(self):
809 return self.GetIssueProperties()['patchsets'][-1]
binji@chromium.org0281f522012-09-14 13:37:59 +0000810
811 def GetPatchSetDiff(self, issue, patchset):
maruel@chromium.org27bb3872011-05-30 20:33:19 +0000812 return self.RpcServer().get(
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000813 '/download/issue%s_%s.diff' % (issue, patchset))
814
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000815 def GetIssueProperties(self):
816 if self._props is None:
817 issue = self.GetIssue()
818 if not issue:
819 self._props = {}
820 else:
821 self._props = self.RpcServer().get_issue_properties(issue, True)
822 return self._props
823
maruel@chromium.orgcf087782013-07-23 13:08:48 +0000824 def GetApprovingReviewers(self):
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000825 return get_approving_reviewers(self.GetIssueProperties())
maruel@chromium.orge52678e2013-04-26 18:34:44 +0000826
apavlov@chromium.orge4efd512014-11-05 09:05:29 +0000827 def AddComment(self, message):
828 return self.RpcServer().add_comment(self.GetIssue(), message)
829
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000830 def SetIssue(self, issue):
831 """Set this branch's issue. If issue=0, clears the issue."""
832 if issue:
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000833 self.issue = issue
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000834 RunGit(['config', self._IssueSetting(), str(issue)])
835 if self.rietveld_server:
836 RunGit(['config', self._RietveldServer(), self.rietveld_server])
837 else:
teravest@chromium.orgd79d4b82013-10-23 20:09:08 +0000838 current_issue = self.GetIssue()
839 if current_issue:
840 RunGit(['config', '--unset', self._IssueSetting()])
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000841 self.issue = None
842 self.SetPatchset(None)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000843
asvitkine@chromium.org15169952011-09-27 14:30:53 +0000844 def GetChange(self, upstream_branch, author):
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000845 if not self.GitSanityChecks(upstream_branch):
846 DieWithError('\nGit sanity check failure')
847
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000848 root = settings.GetRelativeRoot()
bratell@opera.comf267b0e2013-05-02 09:11:43 +0000849 if not root:
850 root = '.'
bauerb@chromium.org512f1ef2011-04-20 15:17:57 +0000851 absroot = os.path.abspath(root)
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000852
853 # We use the sha1 of HEAD as a name of this change.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000854 name = RunGitWithCode(['rev-parse', 'HEAD'])[1].strip()
bauerb@chromium.org512f1ef2011-04-20 15:17:57 +0000855 # Need to pass a relative path for msysgit.
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +0000856 try:
maruel@chromium.org80a9ef12011-12-13 20:44:10 +0000857 files = scm.GIT.CaptureStatus([root], '.', upstream_branch)
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +0000858 except subprocess2.CalledProcessError:
859 DieWithError(
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +0000860 ('\nFailed to diff against upstream branch %s\n\n'
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +0000861 'This branch probably doesn\'t exist anymore. To reset the\n'
862 'tracking branch, please run\n'
863 ' git branch --set-upstream %s trunk\n'
864 'replacing trunk with origin/master or the relevant branch') %
865 (upstream_branch, self.GetBranch()))
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000866
maruel@chromium.org52424302012-08-29 15:14:30 +0000867 issue = self.GetIssue()
868 patchset = self.GetPatchset()
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000869 if issue:
870 description = self.GetDescription()
871 else:
872 # If the change was never uploaded, use the log messages of all commits
873 # up to the branch point, as git cl upload will prefill the description
874 # with these log messages.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000875 args = ['log', '--pretty=format:%s%n%n%b', '%s...' % (upstream_branch)]
876 description = RunGitWithCode(args)[1].strip()
maruel@chromium.org03b3bdc2011-06-14 13:04:12 +0000877
878 if not author:
maruel@chromium.org13f623c2011-07-22 16:02:23 +0000879 author = RunGit(['config', 'user.email']).strip() or None
asvitkine@chromium.org15169952011-09-27 14:30:53 +0000880 return presubmit_support.GitChange(
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000881 name,
882 description,
883 absroot,
884 files,
885 issue,
886 patchset,
agable@chromium.orgea84ef12014-04-30 19:55:12 +0000887 author,
888 upstream=upstream_branch)
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000889
jsbell@chromium.orgb99fbd92014-09-11 17:29:28 +0000890 def GetStatus(self):
891 """Apply a rough heuristic to give a simple summary of an issue's review
892 or CQ status, assuming adherence to a common workflow.
893
894 Returns None if no issue for this branch, or one of the following keywords:
895 * 'error' - error from review tool (including deleted issues)
896 * 'unsent' - not sent for review
897 * 'waiting' - waiting for review
898 * 'reply' - waiting for owner to reply to review
899 * 'lgtm' - LGTM from at least one approved reviewer
900 * 'commit' - in the commit queue
901 * 'closed' - closed
902 """
903 if not self.GetIssue():
904 return None
905
906 try:
907 props = self.GetIssueProperties()
908 except urllib2.HTTPError:
909 return 'error'
910
911 if props.get('closed'):
912 # Issue is closed.
913 return 'closed'
914 if props.get('commit'):
915 # Issue is in the commit queue.
916 return 'commit'
917
918 try:
919 reviewers = self.GetApprovingReviewers()
920 except urllib2.HTTPError:
921 return 'error'
922
923 if reviewers:
924 # Was LGTM'ed.
925 return 'lgtm'
926
927 messages = props.get('messages') or []
928
929 if not messages:
930 # No message was sent.
931 return 'unsent'
932 if messages[-1]['sender'] != props.get('owner_email'):
933 # Non-LGTM reply from non-owner
934 return 'reply'
935 return 'waiting'
936
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +0000937 def RunHook(self, committing, may_prompt, verbose, change):
asvitkine@chromium.org15169952011-09-27 14:30:53 +0000938 """Calls sys.exit() if the hook fails; returns a HookResults otherwise."""
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000939
940 try:
maruel@chromium.orgb0a63912012-01-17 18:10:16 +0000941 return presubmit_support.DoPresubmitChecks(change, committing,
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000942 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin,
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +0000943 default_presubmit=None, may_prompt=may_prompt,
maruel@chromium.org239f4112011-06-03 20:08:23 +0000944 rietveld_obj=self.RpcServer())
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000945 except presubmit_support.PresubmitFailure, e:
946 DieWithError(
947 ('%s\nMaybe your depot_tools is out of date?\n'
948 'If all fails, contact maruel@') % e)
949
maruel@chromium.orgb021b322013-04-08 17:57:29 +0000950 def UpdateDescription(self, description):
951 self.description = description
952 return self.RpcServer().update_description(
953 self.GetIssue(), self.description)
954
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000955 def CloseIssue(self):
maruel@chromium.org607bb1b2011-06-01 23:43:11 +0000956 """Updates the description and closes the issue."""
maruel@chromium.orgb021b322013-04-08 17:57:29 +0000957 return self.RpcServer().close_issue(self.GetIssue())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000958
maruel@chromium.org27bb3872011-05-30 20:33:19 +0000959 def SetFlag(self, flag, value):
960 """Patchset must match."""
961 if not self.GetPatchset():
962 DieWithError('The patchset needs to match. Send another patchset.')
963 try:
964 return self.RpcServer().set_flag(
maruel@chromium.org52424302012-08-29 15:14:30 +0000965 self.GetIssue(), self.GetPatchset(), flag, value)
maruel@chromium.org27bb3872011-05-30 20:33:19 +0000966 except urllib2.HTTPError, e:
967 if e.code == 404:
968 DieWithError('The issue %s doesn\'t exist.' % self.GetIssue())
969 if e.code == 403:
970 DieWithError(
971 ('Access denied to issue %s. Maybe the patchset %s doesn\'t '
972 'match?') % (self.GetIssue(), self.GetPatchset()))
973 raise
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000974
maruel@chromium.orgcab38e92011-04-09 00:30:51 +0000975 def RpcServer(self):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000976 """Returns an upload.RpcServer() to access this review's rietveld instance.
977 """
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000978 if not self._rpc_server:
maruel@chromium.org4bac4b52012-11-27 20:33:52 +0000979 self._rpc_server = rietveld.CachingRietveld(
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +0000980 self.GetRietveldServer(),
981 self._auth_config or auth.make_auth_config())
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000982 return self._rpc_server
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000983
984 def _IssueSetting(self):
985 """Return the git setting that stores this change's issue."""
986 return 'branch.%s.rietveldissue' % self.GetBranch()
987
988 def _PatchsetSetting(self):
989 """Return the git setting that stores this change's most recent patchset."""
990 return 'branch.%s.rietveldpatchset' % self.GetBranch()
991
992 def _RietveldServer(self):
993 """Returns the git setting that stores this change's rietveld server."""
szager@chromium.orgd62c61f2014-10-20 22:33:21 +0000994 branch = self.GetBranch()
995 if branch:
996 return 'branch.%s.rietveldserver' % branch
997 return None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000998
999
1000def GetCodereviewSettingsInteractively():
1001 """Prompt the user for settings."""
ukai@chromium.orge8077812012-02-03 03:41:46 +00001002 # TODO(ukai): ask code review system is rietveld or gerrit?
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001003 server = settings.GetDefaultServerUrl(error_ok=True)
1004 prompt = 'Rietveld server (host[:port])'
1005 prompt += ' [%s]' % (server or DEFAULT_SERVER)
maruel@chromium.org90541732011-04-01 17:54:18 +00001006 newserver = ask_for_data(prompt + ':')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001007 if not server and not newserver:
1008 newserver = DEFAULT_SERVER
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001009 if newserver:
1010 newserver = gclient_utils.UpgradeToHttps(newserver)
1011 if newserver != server:
1012 RunGit(['config', 'rietveld.server', newserver])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001013
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001014 def SetProperty(initial, caption, name, is_url):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001015 prompt = caption
1016 if initial:
1017 prompt += ' ("x" to clear) [%s]' % initial
maruel@chromium.org90541732011-04-01 17:54:18 +00001018 new_val = ask_for_data(prompt + ':')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001019 if new_val == 'x':
1020 RunGit(['config', '--unset-all', 'rietveld.' + name], error_ok=True)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001021 elif new_val:
1022 if is_url:
1023 new_val = gclient_utils.UpgradeToHttps(new_val)
1024 if new_val != initial:
1025 RunGit(['config', 'rietveld.' + name, new_val])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001026
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001027 SetProperty(settings.GetDefaultCCList(), 'CC list', 'cc', False)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001028 SetProperty(settings.GetDefaultPrivateFlag(),
1029 'Private flag (rietveld only)', 'private', False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001030 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL',
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001031 'tree-status-url', False)
1032 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url', True)
rmistry@google.com90752582014-01-14 21:04:50 +00001033 SetProperty(settings.GetBugPrefix(), 'Bug Prefix', 'bug-prefix', False)
rmistry@google.com5626a922015-02-26 14:03:30 +00001034 SetProperty(settings.GetRunPostUploadHook(), 'Run Post Upload Hook',
1035 'run-post-upload-hook', False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001036
1037 # TODO: configure a default branch to diff against, rather than this
1038 # svn-based hackery.
1039
1040
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001041class ChangeDescription(object):
1042 """Contains a parsed form of the change description."""
maruel@chromium.orgc6f60e82013-04-19 17:01:57 +00001043 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$'
agable@chromium.org42c20792013-09-12 17:34:49 +00001044 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$'
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001045
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001046 def __init__(self, description):
agable@chromium.org42c20792013-09-12 17:34:49 +00001047 self._description_lines = (description or '').strip().splitlines()
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001048
agable@chromium.org42c20792013-09-12 17:34:49 +00001049 @property # www.logilab.org/ticket/89786
1050 def description(self): # pylint: disable=E0202
1051 return '\n'.join(self._description_lines)
1052
1053 def set_description(self, desc):
1054 if isinstance(desc, basestring):
1055 lines = desc.splitlines()
1056 else:
1057 lines = [line.rstrip() for line in desc]
1058 while lines and not lines[0]:
1059 lines.pop(0)
1060 while lines and not lines[-1]:
1061 lines.pop(-1)
1062 self._description_lines = lines
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001063
piman@chromium.org336f9122014-09-04 02:16:55 +00001064 def update_reviewers(self, reviewers, add_owners_tbr=False, change=None):
agable@chromium.org42c20792013-09-12 17:34:49 +00001065 """Rewrites the R=/TBR= line(s) as a single line each."""
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001066 assert isinstance(reviewers, list), reviewers
piman@chromium.org336f9122014-09-04 02:16:55 +00001067 if not reviewers and not add_owners_tbr:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001068 return
agable@chromium.org42c20792013-09-12 17:34:49 +00001069 reviewers = reviewers[:]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001070
agable@chromium.org42c20792013-09-12 17:34:49 +00001071 # Get the set of R= and TBR= lines and remove them from the desciption.
1072 regexp = re.compile(self.R_LINE)
1073 matches = [regexp.match(line) for line in self._description_lines]
1074 new_desc = [l for i, l in enumerate(self._description_lines)
1075 if not matches[i]]
1076 self.set_description(new_desc)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001077
agable@chromium.org42c20792013-09-12 17:34:49 +00001078 # Construct new unified R= and TBR= lines.
1079 r_names = []
1080 tbr_names = []
1081 for match in matches:
1082 if not match:
1083 continue
1084 people = cleanup_list([match.group(2).strip()])
1085 if match.group(1) == 'TBR':
1086 tbr_names.extend(people)
1087 else:
1088 r_names.extend(people)
1089 for name in r_names:
1090 if name not in reviewers:
1091 reviewers.append(name)
piman@chromium.org336f9122014-09-04 02:16:55 +00001092 if add_owners_tbr:
1093 owners_db = owners.Database(change.RepositoryRoot(),
1094 fopen=file, os_path=os.path, glob=glob.glob)
1095 all_reviewers = set(tbr_names + reviewers)
1096 missing_files = owners_db.files_not_covered_by(change.LocalPaths(),
1097 all_reviewers)
1098 tbr_names.extend(owners_db.reviewers_for(missing_files,
1099 change.author_email))
agable@chromium.org42c20792013-09-12 17:34:49 +00001100 new_r_line = 'R=' + ', '.join(reviewers) if reviewers else None
1101 new_tbr_line = 'TBR=' + ', '.join(tbr_names) if tbr_names else None
1102
1103 # Put the new lines in the description where the old first R= line was.
1104 line_loc = next((i for i, match in enumerate(matches) if match), -1)
1105 if 0 <= line_loc < len(self._description_lines):
1106 if new_tbr_line:
1107 self._description_lines.insert(line_loc, new_tbr_line)
1108 if new_r_line:
1109 self._description_lines.insert(line_loc, new_r_line)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001110 else:
agable@chromium.org42c20792013-09-12 17:34:49 +00001111 if new_r_line:
1112 self.append_footer(new_r_line)
1113 if new_tbr_line:
1114 self.append_footer(new_tbr_line)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001115
1116 def prompt(self):
1117 """Asks the user to update the description."""
agable@chromium.org42c20792013-09-12 17:34:49 +00001118 self.set_description([
1119 '# Enter a description of the change.',
1120 '# This will be displayed on the codereview site.',
1121 '# The first line will also be used as the subject of the review.',
alancutter@chromium.orgbd1073e2013-06-01 00:34:38 +00001122 '#--------------------This line is 72 characters long'
agable@chromium.org42c20792013-09-12 17:34:49 +00001123 '--------------------',
1124 ] + self._description_lines)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001125
agable@chromium.org42c20792013-09-12 17:34:49 +00001126 regexp = re.compile(self.BUG_LINE)
1127 if not any((regexp.match(line) for line in self._description_lines)):
rmistry@google.com90752582014-01-14 21:04:50 +00001128 self.append_footer('BUG=%s' % settings.GetBugPrefix())
agable@chromium.org42c20792013-09-12 17:34:49 +00001129 content = gclient_utils.RunEditor(self.description, True,
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001130 git_editor=settings.GetGitEditor())
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001131 if not content:
1132 DieWithError('Running editor failed')
agable@chromium.org42c20792013-09-12 17:34:49 +00001133 lines = content.splitlines()
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001134
1135 # Strip off comments.
agable@chromium.org42c20792013-09-12 17:34:49 +00001136 clean_lines = [line.rstrip() for line in lines if not line.startswith('#')]
1137 if not clean_lines:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001138 DieWithError('No CL description, aborting')
agable@chromium.org42c20792013-09-12 17:34:49 +00001139 self.set_description(clean_lines)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001140
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001141 def append_footer(self, line):
agable@chromium.org42c20792013-09-12 17:34:49 +00001142 if self._description_lines:
1143 # Add an empty line if either the last line or the new line isn't a tag.
1144 last_line = self._description_lines[-1]
1145 if (not presubmit_support.Change.TAG_LINE_RE.match(last_line) or
1146 not presubmit_support.Change.TAG_LINE_RE.match(line)):
1147 self._description_lines.append('')
1148 self._description_lines.append(line)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001149
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001150 def get_reviewers(self):
1151 """Retrieves the list of reviewers."""
agable@chromium.org42c20792013-09-12 17:34:49 +00001152 matches = [re.match(self.R_LINE, line) for line in self._description_lines]
1153 reviewers = [match.group(2).strip() for match in matches if match]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001154 return cleanup_list(reviewers)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001155
1156
maruel@chromium.orge52678e2013-04-26 18:34:44 +00001157def get_approving_reviewers(props):
1158 """Retrieves the reviewers that approved a CL from the issue properties with
1159 messages.
1160
1161 Note that the list may contain reviewers that are not committer, thus are not
1162 considered by the CQ.
1163 """
1164 return sorted(
1165 set(
1166 message['sender']
1167 for message in props['messages']
1168 if message['approval'] and message['sender'] in props['reviewers']
1169 )
1170 )
1171
1172
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001173def FindCodereviewSettingsFile(filename='codereview.settings'):
1174 """Finds the given file starting in the cwd and going up.
1175
1176 Only looks up to the top of the repository unless an
1177 'inherit-review-settings-ok' file exists in the root of the repository.
1178 """
1179 inherit_ok_file = 'inherit-review-settings-ok'
1180 cwd = os.getcwd()
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001181 root = settings.GetRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001182 if os.path.isfile(os.path.join(root, inherit_ok_file)):
1183 root = '/'
1184 while True:
1185 if filename in os.listdir(cwd):
1186 if os.path.isfile(os.path.join(cwd, filename)):
1187 return open(os.path.join(cwd, filename))
1188 if cwd == root:
1189 break
1190 cwd = os.path.dirname(cwd)
1191
1192
1193def LoadCodereviewSettingsFromFile(fileobj):
1194 """Parse a codereview.settings file and updates hooks."""
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001195 keyvals = gclient_utils.ParseCodereviewSettingsContent(fileobj.read())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001196
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001197 def SetProperty(name, setting, unset_error_ok=False):
1198 fullname = 'rietveld.' + name
1199 if setting in keyvals:
1200 RunGit(['config', fullname, keyvals[setting]])
1201 else:
1202 RunGit(['config', '--unset-all', fullname], error_ok=unset_error_ok)
1203
1204 SetProperty('server', 'CODE_REVIEW_SERVER')
1205 # Only server setting is required. Other settings can be absent.
1206 # In that case, we ignore errors raised during option deletion attempt.
1207 SetProperty('cc', 'CC_LIST', unset_error_ok=True)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001208 SetProperty('private', 'PRIVATE', unset_error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001209 SetProperty('tree-status-url', 'STATUS', unset_error_ok=True)
1210 SetProperty('viewvc-url', 'VIEW_VC', unset_error_ok=True)
rmistry@google.com90752582014-01-14 21:04:50 +00001211 SetProperty('bug-prefix', 'BUG_PREFIX', unset_error_ok=True)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001212 SetProperty('cpplint-regex', 'LINT_REGEX', unset_error_ok=True)
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00001213 SetProperty('force-https-commit-url', 'FORCE_HTTPS_COMMIT_URL',
1214 unset_error_ok=True)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001215 SetProperty('cpplint-ignore-regex', 'LINT_IGNORE_REGEX', unset_error_ok=True)
sheyang@chromium.org152cf832014-06-11 21:37:49 +00001216 SetProperty('project', 'PROJECT', unset_error_ok=True)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00001217 SetProperty('pending-ref-prefix', 'PENDING_REF_PREFIX', unset_error_ok=True)
rmistry@google.com5626a922015-02-26 14:03:30 +00001218 SetProperty('run-post-upload-hook', 'RUN_POST_UPLOAD_HOOK',
1219 unset_error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001220
ukai@chromium.org7044efc2013-11-28 01:51:21 +00001221 if 'GERRIT_HOST' in keyvals:
ukai@chromium.orge8077812012-02-03 03:41:46 +00001222 RunGit(['config', 'gerrit.host', keyvals['GERRIT_HOST']])
ukai@chromium.orge8077812012-02-03 03:41:46 +00001223
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001224 if 'PUSH_URL_CONFIG' in keyvals and 'ORIGIN_URL_CONFIG' in keyvals:
1225 #should be of the form
1226 #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof
1227 #ORIGIN_URL_CONFIG: http://src.chromium.org/git
1228 RunGit(['config', keyvals['PUSH_URL_CONFIG'],
1229 keyvals['ORIGIN_URL_CONFIG']])
1230
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001231
joshua.lock@intel.com426f69b2012-08-02 23:41:49 +00001232def urlretrieve(source, destination):
1233 """urllib is broken for SSL connections via a proxy therefore we
1234 can't use urllib.urlretrieve()."""
1235 with open(destination, 'w') as f:
1236 f.write(urllib2.urlopen(source).read())
1237
1238
ukai@chromium.org712d6102013-11-27 00:52:58 +00001239def hasSheBang(fname):
1240 """Checks fname is a #! script."""
1241 with open(fname) as f:
1242 return f.read(2).startswith('#!')
1243
1244
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001245def DownloadHooks(force):
1246 """downloads hooks
1247
1248 Args:
1249 force: True to update hooks. False to install hooks if not present.
1250 """
1251 if not settings.GetIsGerrit():
1252 return
ukai@chromium.org712d6102013-11-27 00:52:58 +00001253 src = 'https://gerrit-review.googlesource.com/tools/hooks/commit-msg'
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001254 dst = os.path.join(settings.GetRoot(), '.git', 'hooks', 'commit-msg')
1255 if not os.access(dst, os.X_OK):
1256 if os.path.exists(dst):
1257 if not force:
1258 return
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001259 try:
joshua.lock@intel.com426f69b2012-08-02 23:41:49 +00001260 urlretrieve(src, dst)
ukai@chromium.org712d6102013-11-27 00:52:58 +00001261 if not hasSheBang(dst):
1262 DieWithError('Not a script: %s\n'
1263 'You need to download from\n%s\n'
1264 'into .git/hooks/commit-msg and '
1265 'chmod +x .git/hooks/commit-msg' % (dst, src))
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001266 os.chmod(dst, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
1267 except Exception:
1268 if os.path.exists(dst):
1269 os.remove(dst)
ukai@chromium.org712d6102013-11-27 00:52:58 +00001270 DieWithError('\nFailed to download hooks.\n'
1271 'You need to download from\n%s\n'
1272 'into .git/hooks/commit-msg and '
1273 'chmod +x .git/hooks/commit-msg' % src)
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001274
1275
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001276@subcommand.usage('[repo root containing codereview.settings]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001277def CMDconfig(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001278 """Edits configuration for this tree."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001279
pgervais@chromium.org87884cc2014-01-03 22:23:41 +00001280 parser.add_option('--activate-update', action='store_true',
1281 help='activate auto-updating [rietveld] section in '
1282 '.git/config')
1283 parser.add_option('--deactivate-update', action='store_true',
1284 help='deactivate auto-updating [rietveld] section in '
1285 '.git/config')
1286 options, args = parser.parse_args(args)
1287
1288 if options.deactivate_update:
1289 RunGit(['config', 'rietveld.autoupdate', 'false'])
1290 return
1291
1292 if options.activate_update:
1293 RunGit(['config', '--unset', 'rietveld.autoupdate'])
1294 return
1295
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001296 if len(args) == 0:
1297 GetCodereviewSettingsInteractively()
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001298 DownloadHooks(True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001299 return 0
1300
1301 url = args[0]
1302 if not url.endswith('codereview.settings'):
1303 url = os.path.join(url, 'codereview.settings')
1304
1305 # Load code review settings and download hooks (if available).
1306 LoadCodereviewSettingsFromFile(urllib2.urlopen(url))
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001307 DownloadHooks(True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001308 return 0
1309
1310
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001311def CMDbaseurl(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001312 """Gets or sets base-url for this branch."""
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001313 branchref = RunGit(['symbolic-ref', 'HEAD']).strip()
1314 branch = ShortBranchName(branchref)
1315 _, args = parser.parse_args(args)
1316 if not args:
1317 print("Current base-url:")
1318 return RunGit(['config', 'branch.%s.base-url' % branch],
1319 error_ok=False).strip()
1320 else:
1321 print("Setting base-url to %s" % args[0])
1322 return RunGit(['config', 'branch.%s.base-url' % branch, args[0]],
1323 error_ok=False).strip()
1324
1325
jsbell@chromium.orgb99fbd92014-09-11 17:29:28 +00001326def color_for_status(status):
1327 """Maps a Changelist status to color, for CMDstatus and other tools."""
1328 return {
1329 'unsent': Fore.RED,
1330 'waiting': Fore.BLUE,
1331 'reply': Fore.YELLOW,
1332 'lgtm': Fore.GREEN,
1333 'commit': Fore.MAGENTA,
1334 'closed': Fore.CYAN,
1335 'error': Fore.WHITE,
1336 }.get(status, Fore.WHITE)
1337
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001338def fetch_cl_status(b, auth_config=None):
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001339 """Fetches information for an issue and returns (branch, issue, color)."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001340 c = Changelist(branchref=b, auth_config=auth_config)
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001341 i = c.GetIssueURL()
1342 status = c.GetStatus()
1343 color = color_for_status(status)
1344
1345 if i and (not status or status == 'error'):
1346 # The issue probably doesn't exist anymore.
1347 i += ' (broken)'
1348
1349 return (b, i, color)
1350
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001351def get_cl_statuses(
1352 branches, fine_grained, max_processes=None, auth_config=None):
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001353 """Returns a blocking iterable of (branch, issue, color) for given branches.
1354
1355 If fine_grained is true, this will fetch CL statuses from the server.
1356 Otherwise, simply indicate if there's a matching url for the given branches.
1357
1358 If max_processes is specified, it is used as the maximum number of processes
1359 to spawn to fetch CL status from the server. Otherwise 1 process per branch is
1360 spawned.
1361 """
1362 # Silence upload.py otherwise it becomes unwieldly.
1363 upload.verbosity = 0
1364
1365 if fine_grained:
1366 # Process one branch synchronously to work through authentication, then
1367 # spawn processes to process all the other branches in parallel.
1368 if branches:
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001369 fetch = lambda branch: fetch_cl_status(branch, auth_config=auth_config)
1370 yield fetch(branches[0])
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001371
1372 branches_to_fetch = branches[1:]
1373 pool = ThreadPool(
1374 min(max_processes, len(branches_to_fetch))
1375 if max_processes is not None
1376 else len(branches_to_fetch))
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001377 for x in pool.imap_unordered(fetch, branches_to_fetch):
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001378 yield x
1379 else:
1380 # Do not use GetApprovingReviewers(), since it requires an HTTP request.
1381 for b in branches:
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001382 c = Changelist(branchref=b, auth_config=auth_config)
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001383 url = c.GetIssueURL()
1384 yield (b, url, Fore.BLUE if url else Fore.WHITE)
jsbell@chromium.orgb99fbd92014-09-11 17:29:28 +00001385
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001386def CMDstatus(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001387 """Show status of changelists.
1388
1389 Colors are used to tell the state of the CL unless --fast is used:
jsbell@chromium.orgaeab41a2013-12-10 20:01:22 +00001390 - Red not sent for review or broken
1391 - Blue waiting for review
1392 - Yellow waiting for you to reply to review
1393 - Green LGTM'ed
1394 - Magenta in the commit queue
1395 - Cyan was committed, branch can be deleted
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001396
1397 Also see 'git cl comments'.
1398 """
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001399 parser.add_option('--field',
1400 help='print only specific field (desc|id|patch|url)')
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001401 parser.add_option('-f', '--fast', action='store_true',
1402 help='Do not retrieve review status')
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001403 parser.add_option(
1404 '-j', '--maxjobs', action='store', type=int,
1405 help='The maximum number of jobs to use when retrieving review status')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001406
1407 auth.add_auth_options(parser)
1408 options, args = parser.parse_args(args)
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001409 if args:
1410 parser.error('Unsupported args: %s' % args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001411 auth_config = auth.extract_auth_config_from_options(options)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001412
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001413 if options.field:
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001414 cl = Changelist(auth_config=auth_config)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001415 if options.field.startswith('desc'):
1416 print cl.GetDescription()
1417 elif options.field == 'id':
1418 issueid = cl.GetIssue()
1419 if issueid:
1420 print issueid
1421 elif options.field == 'patch':
1422 patchset = cl.GetPatchset()
1423 if patchset:
1424 print patchset
1425 elif options.field == 'url':
1426 url = cl.GetIssueURL()
1427 if url:
1428 print url
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001429 return 0
1430
1431 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads'])
1432 if not branches:
1433 print('No local branch found.')
1434 return 0
1435
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001436 changes = (
1437 Changelist(branchref=b, auth_config=auth_config)
1438 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,
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001444 max_processes=options.maxjobs,
1445 auth_config=auth_config)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001446
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001447 branch_statuses = {}
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001448 alignment = max(5, max(len(ShortBranchName(b)) for b in branches))
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001449 for branch in sorted(branches):
calamity@chromium.orgffde55c2015-03-12 00:44:17 +00001450 while branch not in branch_statuses:
1451 b, i, color = output.next()
1452 branch_statuses[b] = (i, color)
1453 issue, color = branch_statuses.pop(branch)
maruel@chromium.org885f6512013-07-27 02:17:26 +00001454 reset = Fore.RESET
1455 if not sys.stdout.isatty():
1456 color = ''
1457 reset = ''
binji@chromium.orgc3d17dd2013-12-19 00:55:31 +00001458 print ' %*s : %s%s%s' % (
maruel@chromium.org885f6512013-07-27 02:17:26 +00001459 alignment, ShortBranchName(branch), color, issue, reset)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001460
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001461 cl = Changelist(auth_config=auth_config)
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001462 print
1463 print 'Current branch:',
1464 if not cl.GetIssue():
1465 print 'no issue assigned.'
1466 return 0
1467 print cl.GetBranch()
1468 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL())
maruel@chromium.org85616e02014-07-28 15:37:55 +00001469 if not options.fast:
1470 print 'Issue description:'
1471 print cl.GetDescription(pretty=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001472 return 0
1473
1474
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001475def colorize_CMDstatus_doc():
1476 """To be called once in main() to add colors to git cl status help."""
1477 colors = [i for i in dir(Fore) if i[0].isupper()]
1478
1479 def colorize_line(line):
1480 for color in colors:
1481 if color in line.upper():
1482 # Extract whitespaces first and the leading '-'.
1483 indent = len(line) - len(line.lstrip(' ')) + 1
1484 return line[:indent] + getattr(Fore, color) + line[indent:] + Fore.RESET
1485 return line
1486
1487 lines = CMDstatus.__doc__.splitlines()
1488 CMDstatus.__doc__ = '\n'.join(colorize_line(l) for l in lines)
1489
1490
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001491@subcommand.usage('[issue_number]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001492def CMDissue(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001493 """Sets or displays the current code review issue number.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001494
1495 Pass issue number 0 to clear the current issue.
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001496 """
dnj@chromium.org406c4402015-03-03 17:22:28 +00001497 parser.add_option('-r', '--reverse', action='store_true',
1498 help='Lookup the branch(es) for the specified issues. If '
1499 'no issues are specified, all branches with mapped '
1500 'issues will be listed.')
1501 options, args = parser.parse_args(args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001502
dnj@chromium.org406c4402015-03-03 17:22:28 +00001503 if options.reverse:
1504 branches = RunGit(['for-each-ref', 'refs/heads',
1505 '--format=%(refname:short)']).splitlines()
1506
1507 # Reverse issue lookup.
1508 issue_branch_map = {}
1509 for branch in branches:
1510 cl = Changelist(branchref=branch)
1511 issue_branch_map.setdefault(cl.GetIssue(), []).append(branch)
1512 if not args:
1513 args = sorted(issue_branch_map.iterkeys())
1514 for issue in args:
1515 if not issue:
1516 continue
1517 print 'Branch for issue number %s: %s' % (
1518 issue, ', '.join(issue_branch_map.get(int(issue)) or ('None',)))
1519 else:
1520 cl = Changelist()
1521 if len(args) > 0:
1522 try:
1523 issue = int(args[0])
1524 except ValueError:
1525 DieWithError('Pass a number to set the issue or none to list it.\n'
1526 'Maybe you want to run git cl status?')
1527 cl.SetIssue(issue)
1528 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001529 return 0
1530
1531
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001532def CMDcomments(parser, args):
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001533 """Shows or posts review comments for any changelist."""
1534 parser.add_option('-a', '--add-comment', dest='comment',
1535 help='comment to add to an issue')
1536 parser.add_option('-i', dest='issue',
1537 help="review issue id (defaults to current issue)")
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001538 auth.add_auth_options(parser)
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001539 options, args = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001540 auth_config = auth.extract_auth_config_from_options(options)
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001541
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001542 issue = None
1543 if options.issue:
1544 try:
1545 issue = int(options.issue)
1546 except ValueError:
1547 DieWithError('A review issue id is expected to be a number')
1548
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001549 cl = Changelist(issue=issue, auth_config=auth_config)
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001550
1551 if options.comment:
1552 cl.AddComment(options.comment)
1553 return 0
1554
1555 data = cl.GetIssueProperties()
maruel@chromium.org5cab2d32014-11-11 18:32:41 +00001556 for message in sorted(data.get('messages', []), key=lambda x: x['date']):
apavlov@chromium.orge4efd512014-11-05 09:05:29 +00001557 if message['disapproval']:
1558 color = Fore.RED
1559 elif message['approval']:
1560 color = Fore.GREEN
1561 elif message['sender'] == data['owner_email']:
1562 color = Fore.MAGENTA
1563 else:
1564 color = Fore.BLUE
1565 print '\n%s%s %s%s' % (
1566 color, message['date'].split('.', 1)[0], message['sender'],
1567 Fore.RESET)
1568 if message['text'].strip():
1569 print '\n'.join(' ' + l for l in message['text'].splitlines())
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001570 return 0
1571
1572
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001573def CMDdescription(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001574 """Brings up the editor for the current CL's description."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001575 auth.add_auth_options(parser)
1576 options, _ = parser.parse_args(args)
1577 auth_config = auth.extract_auth_config_from_options(options)
1578 cl = Changelist(auth_config=auth_config)
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001579 if not cl.GetIssue():
1580 DieWithError('This branch has no associated changelist.')
1581 description = ChangeDescription(cl.GetDescription())
1582 description.prompt()
wychen@chromium.org063e4e52015-04-03 06:51:44 +00001583 if cl.GetDescription() != description.description:
1584 cl.UpdateDescription(description.description)
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001585 return 0
1586
1587
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001588def CreateDescriptionFromLog(args):
1589 """Pulls out the commit log to use as a base for the CL description."""
1590 log_args = []
1591 if len(args) == 1 and not args[0].endswith('.'):
1592 log_args = [args[0] + '..']
1593 elif len(args) == 1 and args[0].endswith('...'):
1594 log_args = [args[0][:-1]]
1595 elif len(args) == 2:
1596 log_args = [args[0] + '..' + args[1]]
1597 else:
1598 log_args = args[:] # Hope for the best!
maruel@chromium.org373af802012-05-25 21:07:33 +00001599 return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001600
1601
thestig@chromium.org44202a22014-03-11 19:22:18 +00001602def CMDlint(parser, args):
1603 """Runs cpplint on the current changelist."""
tzik@chromium.orgf204d4b2014-03-13 07:40:55 +00001604 parser.add_option('--filter', action='append', metavar='-x,+y',
1605 help='Comma-separated list of cpplint\'s category-filters')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001606 auth.add_auth_options(parser)
1607 options, args = parser.parse_args(args)
1608 auth_config = auth.extract_auth_config_from_options(options)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001609
1610 # Access to a protected member _XX of a client class
1611 # pylint: disable=W0212
1612 try:
1613 import cpplint
1614 import cpplint_chromium
1615 except ImportError:
1616 print "Your depot_tools is missing cpplint.py and/or cpplint_chromium.py."
1617 return 1
1618
1619 # Change the current working directory before calling lint so that it
1620 # shows the correct base.
1621 previous_cwd = os.getcwd()
1622 os.chdir(settings.GetRoot())
1623 try:
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001624 cl = Changelist(auth_config=auth_config)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001625 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
1626 files = [f.LocalPath() for f in change.AffectedFiles()]
thestig@chromium.org5839eb52014-05-30 16:20:51 +00001627 if not files:
1628 print "Cannot lint an empty CL"
1629 return 1
thestig@chromium.org44202a22014-03-11 19:22:18 +00001630
1631 # Process cpplints arguments if any.
tzik@chromium.orgf204d4b2014-03-13 07:40:55 +00001632 command = args + files
1633 if options.filter:
1634 command = ['--filter=' + ','.join(options.filter)] + command
1635 filenames = cpplint.ParseArguments(command)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001636
1637 white_regex = re.compile(settings.GetLintRegex())
1638 black_regex = re.compile(settings.GetLintIgnoreRegex())
1639 extra_check_functions = [cpplint_chromium.CheckPointerDeclarationWhitespace]
1640 for filename in filenames:
1641 if white_regex.match(filename):
1642 if black_regex.match(filename):
1643 print "Ignoring file %s" % filename
1644 else:
1645 cpplint.ProcessFile(filename, cpplint._cpplint_state.verbose_level,
1646 extra_check_functions)
1647 else:
1648 print "Skipping file %s" % filename
1649 finally:
1650 os.chdir(previous_cwd)
1651 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count
1652 if cpplint._cpplint_state.error_count != 0:
1653 return 1
1654 return 0
1655
1656
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001657def CMDpresubmit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001658 """Runs presubmit tests on the current changelist."""
ilevy@chromium.org375a9022013-01-07 01:12:05 +00001659 parser.add_option('-u', '--upload', action='store_true',
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001660 help='Run upload hook instead of the push/dcommit hook')
ilevy@chromium.org375a9022013-01-07 01:12:05 +00001661 parser.add_option('-f', '--force', action='store_true',
sbc@chromium.org495ad152012-09-04 23:07:42 +00001662 help='Run checks even if tree is dirty')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001663 auth.add_auth_options(parser)
1664 options, args = parser.parse_args(args)
1665 auth_config = auth.extract_auth_config_from_options(options)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001666
sbc@chromium.org71437c02015-04-09 19:29:40 +00001667 if not options.force and git_common.is_dirty_git_tree('presubmit'):
ukai@chromium.org259e4682012-10-25 07:36:33 +00001668 print 'use --force to check even if tree is dirty.'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001669 return 1
1670
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001671 cl = Changelist(auth_config=auth_config)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001672 if args:
1673 base_branch = args[0]
1674 else:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00001675 # Default to diffing against the common ancestor of the upstream branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001676 base_branch = cl.GetCommonAncestorWithUpstream()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001677
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00001678 cl.RunHook(
1679 committing=not options.upload,
1680 may_prompt=False,
1681 verbose=options.verbose,
1682 change=cl.GetChange(base_branch, None))
dpranke@chromium.org0a2bb372011-03-25 01:16:22 +00001683 return 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001684
1685
sivachandra@chromium.orgaebe87f2012-10-22 20:34:21 +00001686def AddChangeIdToCommitMessage(options, args):
1687 """Re-commits using the current message, assumes the commit hook is in
1688 place.
1689 """
1690 log_desc = options.message or CreateDescriptionFromLog(args)
1691 git_command = ['commit', '--amend', '-m', log_desc]
1692 RunGit(git_command)
1693 new_log_desc = CreateDescriptionFromLog(args)
1694 if CHANGE_ID in new_log_desc:
1695 print 'git-cl: Added Change-Id to commit message.'
1696 else:
1697 print >> sys.stderr, 'ERROR: Gerrit commit-msg hook not available.'
1698
1699
piman@chromium.org336f9122014-09-04 02:16:55 +00001700def GerritUpload(options, args, cl, change):
ukai@chromium.orge8077812012-02-03 03:41:46 +00001701 """upload the current branch to gerrit."""
1702 # We assume the remote called "origin" is the one we want.
1703 # It is probably not worthwhile to support different workflows.
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001704 gerrit_remote = 'origin'
ukai@chromium.orge8077812012-02-03 03:41:46 +00001705 branch = 'master'
1706 if options.target_branch:
1707 branch = options.target_branch
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001708
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001709 change_desc = ChangeDescription(
1710 options.message or CreateDescriptionFromLog(args))
1711 if not change_desc.description:
ukai@chromium.orge8077812012-02-03 03:41:46 +00001712 print "Description is empty; aborting."
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001713 return 1
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00001714
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001715 if options.squash:
1716 # Try to get the message from a previous upload.
1717 shadow_branch = 'refs/heads/git_cl_uploads/' + cl.GetBranch()
1718 message = RunGitSilent(['show', '--format=%s\n\n%b', '-s', shadow_branch])
1719 if not message:
1720 if not options.force:
1721 change_desc.prompt()
1722
1723 if CHANGE_ID not in change_desc.description:
1724 # Run the commit-msg hook without modifying the head commit by writing
1725 # the commit message to a temporary file and running the hook over it,
1726 # then reading the file back in.
1727 commit_msg_hook = os.path.join(settings.GetRoot(), '.git', 'hooks',
1728 'commit-msg')
1729 file_handle, msg_file = tempfile.mkstemp(text=True,
1730 prefix='commit_msg')
1731 try:
1732 try:
1733 with os.fdopen(file_handle, 'w') as fileobj:
1734 fileobj.write(change_desc.description)
1735 finally:
1736 os.close(file_handle)
1737 RunCommand([commit_msg_hook, msg_file])
1738 change_desc.set_description(gclient_utils.FileRead(msg_file))
1739 finally:
1740 os.remove(msg_file)
1741
1742 if not change_desc.description:
1743 print "Description is empty; aborting."
1744 return 1
1745
1746 message = change_desc.description
1747
1748 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
1749 if remote is '.':
1750 # If our upstream branch is local, we base our squashed commit on its
1751 # squashed version.
1752 parent = ('refs/heads/git_cl_uploads/' +
1753 scm.GIT.ShortBranchName(upstream_branch))
1754
1755 # Verify that the upstream branch has been uploaded too, otherwise Gerrit
1756 # will create additional CLs when uploading.
1757 if (RunGitSilent(['rev-parse', upstream_branch + ':']) !=
1758 RunGitSilent(['rev-parse', parent + ':'])):
1759 print 'Upload upstream branch ' + upstream_branch + ' first.'
1760 return 1
1761 else:
1762 parent = cl.GetCommonAncestorWithUpstream()
1763
1764 tree = RunGit(['rev-parse', 'HEAD:']).strip()
1765 ref_to_push = RunGit(['commit-tree', tree, '-p', parent,
1766 '-m', message]).strip()
1767 else:
1768 if CHANGE_ID not in change_desc.description:
1769 AddChangeIdToCommitMessage(options, args)
1770 ref_to_push = 'HEAD'
1771 parent = '%s/%s' % (gerrit_remote, branch)
1772
1773 commits = RunGitSilent(['rev-list', '%s..%s' % (parent,
1774 ref_to_push)]).splitlines()
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00001775 if len(commits) > 1:
1776 print('WARNING: This will upload %d commits. Run the following command '
1777 'to see which commits will be uploaded: ' % len(commits))
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001778 print('git log %s..%s' % (parent, ref_to_push))
1779 print('You can also use `git squash-branch` to squash these into a single '
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00001780 'commit.')
1781 ask_for_data('About to upload; enter to confirm.')
1782
piman@chromium.org336f9122014-09-04 02:16:55 +00001783 if options.reviewers or options.tbr_owners:
1784 change_desc.update_reviewers(options.reviewers, options.tbr_owners, change)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001785
ukai@chromium.orge8077812012-02-03 03:41:46 +00001786 receive_options = []
1787 cc = cl.GetCCList().split(',')
1788 if options.cc:
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00001789 cc.extend(options.cc)
ukai@chromium.orge8077812012-02-03 03:41:46 +00001790 cc = filter(None, cc)
1791 if cc:
1792 receive_options += ['--cc=' + email for email in cc]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001793 if change_desc.get_reviewers():
1794 receive_options.extend(
1795 '--reviewer=' + email for email in change_desc.get_reviewers())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001796
ukai@chromium.orge8077812012-02-03 03:41:46 +00001797 git_command = ['push']
1798 if receive_options:
ukai@chromium.org19bbfa22012-02-03 16:18:11 +00001799 git_command.append('--receive-pack=git receive-pack %s' %
ukai@chromium.orge8077812012-02-03 03:41:46 +00001800 ' '.join(receive_options))
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001801 git_command += [gerrit_remote, ref_to_push + ':refs/for/' + branch]
ukai@chromium.orge8077812012-02-03 03:41:46 +00001802 RunGit(git_command)
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001803
1804 if options.squash:
1805 head = RunGit(['rev-parse', 'HEAD']).strip()
1806 RunGit(['update-ref', '-m', 'Uploaded ' + head, shadow_branch, ref_to_push])
1807
ukai@chromium.orge8077812012-02-03 03:41:46 +00001808 # TODO(ukai): parse Change-Id: and set issue number?
1809 return 0
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001810
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001811
wittman@chromium.org455dc922015-01-26 20:15:50 +00001812def GetTargetRef(remote, remote_branch, target_branch, pending_prefix):
1813 """Computes the remote branch ref to use for the CL.
1814
1815 Args:
1816 remote (str): The git remote for the CL.
1817 remote_branch (str): The git remote branch for the CL.
1818 target_branch (str): The target branch specified by the user.
1819 pending_prefix (str): The pending prefix from the settings.
1820 """
1821 if not (remote and remote_branch):
1822 return None
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001823
wittman@chromium.org455dc922015-01-26 20:15:50 +00001824 if target_branch:
1825 # Cannonicalize branch references to the equivalent local full symbolic
1826 # refs, which are then translated into the remote full symbolic refs
1827 # below.
1828 if '/' not in target_branch:
1829 remote_branch = 'refs/remotes/%s/%s' % (remote, target_branch)
1830 else:
1831 prefix_replacements = (
1832 ('^((refs/)?remotes/)?branch-heads/', 'refs/remotes/branch-heads/'),
1833 ('^((refs/)?remotes/)?%s/' % remote, 'refs/remotes/%s/' % remote),
1834 ('^(refs/)?heads/', 'refs/remotes/%s/' % remote),
1835 )
1836 match = None
1837 for regex, replacement in prefix_replacements:
1838 match = re.search(regex, target_branch)
1839 if match:
1840 remote_branch = target_branch.replace(match.group(0), replacement)
1841 break
1842 if not match:
1843 # This is a branch path but not one we recognize; use as-is.
1844 remote_branch = target_branch
rmistry@google.comc68112d2015-03-03 12:48:06 +00001845 elif remote_branch in REFS_THAT_ALIAS_TO_OTHER_REFS:
1846 # Handle the refs that need to land in different refs.
1847 remote_branch = REFS_THAT_ALIAS_TO_OTHER_REFS[remote_branch]
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00001848
wittman@chromium.org455dc922015-01-26 20:15:50 +00001849 # Create the true path to the remote branch.
1850 # Does the following translation:
1851 # * refs/remotes/origin/refs/diff/test -> refs/diff/test
1852 # * refs/remotes/origin/master -> refs/heads/master
1853 # * refs/remotes/branch-heads/test -> refs/branch-heads/test
1854 if remote_branch.startswith('refs/remotes/%s/refs/' % remote):
1855 remote_branch = remote_branch.replace('refs/remotes/%s/' % remote, '')
1856 elif remote_branch.startswith('refs/remotes/%s/' % remote):
1857 remote_branch = remote_branch.replace('refs/remotes/%s/' % remote,
1858 'refs/heads/')
1859 elif remote_branch.startswith('refs/remotes/branch-heads'):
1860 remote_branch = remote_branch.replace('refs/remotes/', 'refs/')
1861 # If a pending prefix exists then replace refs/ with it.
1862 if pending_prefix:
1863 remote_branch = remote_branch.replace('refs/', pending_prefix)
1864 return remote_branch
1865
1866
piman@chromium.org336f9122014-09-04 02:16:55 +00001867def RietveldUpload(options, args, cl, change):
ukai@chromium.orge8077812012-02-03 03:41:46 +00001868 """upload the patch to rietveld."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001869 upload_args = ['--assume_yes'] # Don't ask about untracked files.
1870 upload_args.extend(['--server', cl.GetRietveldServer()])
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00001871 upload_args.extend(auth.auth_config_to_command_options(cl.auth_config))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001872 if options.emulate_svn_auto_props:
1873 upload_args.append('--emulate_svn_auto_props')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001874
1875 change_desc = None
1876
pgervais@chromium.org91141372014-01-09 23:27:20 +00001877 if options.email is not None:
1878 upload_args.extend(['--email', options.email])
1879
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001880 if cl.GetIssue():
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00001881 if options.title:
1882 upload_args.extend(['--title', options.title])
rogerta@chromium.orgafadfca2013-05-29 14:15:53 +00001883 if options.message:
1884 upload_args.extend(['--message', options.message])
maruel@chromium.org52424302012-08-29 15:14:30 +00001885 upload_args.extend(['--issue', str(cl.GetIssue())])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001886 print ("This branch is associated with issue %s. "
1887 "Adding patch to that issue." % cl.GetIssue())
1888 else:
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00001889 if options.title:
1890 upload_args.extend(['--title', options.title])
rogerta@chromium.org43e34f02013-03-25 14:52:48 +00001891 message = options.title or options.message or CreateDescriptionFromLog(args)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001892 change_desc = ChangeDescription(message)
piman@chromium.org336f9122014-09-04 02:16:55 +00001893 if options.reviewers or options.tbr_owners:
1894 change_desc.update_reviewers(options.reviewers,
1895 options.tbr_owners,
1896 change)
maruel@chromium.org71e12a92012-02-14 02:34:15 +00001897 if not options.force:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001898 change_desc.prompt()
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001899
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001900 if not change_desc.description:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001901 print "Description is empty; aborting."
1902 return 1
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001903
maruel@chromium.org71e12a92012-02-14 02:34:15 +00001904 upload_args.extend(['--message', change_desc.description])
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001905 if change_desc.get_reviewers():
1906 upload_args.append('--reviewers=' + ','.join(change_desc.get_reviewers()))
maruel@chromium.orga3353652011-11-30 14:26:57 +00001907 if options.send_mail:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001908 if not change_desc.get_reviewers():
maruel@chromium.orga3353652011-11-30 14:26:57 +00001909 DieWithError("Must specify reviewers to send email.")
1910 upload_args.append('--send_mail')
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +00001911
1912 # We check this before applying rietveld.private assuming that in
1913 # rietveld.cc only addresses which we can send private CLs to are listed
1914 # if rietveld.private is set, and so we should ignore rietveld.cc only when
1915 # --private is specified explicitly on the command line.
1916 if options.private:
1917 logging.warn('rietveld.cc is ignored since private flag is specified. '
1918 'You need to review and add them manually if necessary.')
1919 cc = cl.GetCCListWithoutDefault()
1920 else:
1921 cc = cl.GetCCList()
1922 cc = ','.join(filter(None, (cc, ','.join(options.cc))))
maruel@chromium.orgb2a7c332011-02-25 20:30:37 +00001923 if cc:
1924 upload_args.extend(['--cc', cc])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001925
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001926 if options.private or settings.GetDefaultPrivateFlag() == "True":
1927 upload_args.append('--private')
1928
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00001929 upload_args.extend(['--git_similarity', str(options.similarity)])
iannucci@chromium.org79540052012-10-19 23:15:26 +00001930 if not options.find_copies:
1931 upload_args.extend(['--git_no_find_copies'])
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00001932
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001933 # Include the upstream repo's URL in the change -- this is useful for
1934 # projects that have their source spread across multiple repos.
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001935 remote_url = cl.GetGitBaseUrlFromConfig()
1936 if not remote_url:
1937 if settings.GetIsGitSvn():
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00001938 remote_url = cl.GetGitSvnRemoteUrl()
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001939 else:
jam@chromium.org80c51ae2014-10-17 18:43:02 +00001940 if cl.GetRemoteUrl() and '/' in cl.GetUpstreamBranch():
1941 remote_url = (cl.GetRemoteUrl() + '@'
1942 + cl.GetUpstreamBranch().split('/')[-1])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001943 if remote_url:
1944 upload_args.extend(['--base_url', remote_url])
rmistry@google.comd1e37582014-12-10 20:58:24 +00001945 remote, remote_branch = cl.GetRemoteBranch()
wittman@chromium.org455dc922015-01-26 20:15:50 +00001946 target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
1947 settings.GetPendingRefPrefix())
1948 if target_ref:
1949 upload_args.extend(['--target_ref', target_ref])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001950
sheyang@chromium.org152cf832014-06-11 21:37:49 +00001951 project = settings.GetProject()
1952 if project:
1953 upload_args.extend(['--project', project])
1954
rmistry@google.comef966222015-04-07 11:15:01 +00001955 if options.cq_dry_run:
1956 upload_args.extend(['--cq_dry_run'])
1957
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001958 try:
ilevy@chromium.org82880192012-11-26 15:41:57 +00001959 upload_args = ['upload'] + upload_args + args
1960 logging.info('upload.RealMain(%s)', upload_args)
1961 issue, patchset = upload.RealMain(upload_args)
maruel@chromium.org911fce12013-07-29 23:01:13 +00001962 issue = int(issue)
1963 patchset = int(patchset)
maruel@chromium.org9ce0dff2011-04-04 17:56:50 +00001964 except KeyboardInterrupt:
1965 sys.exit(1)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001966 except:
1967 # If we got an exception after the user typed a description for their
1968 # change, back up the description before re-raising.
1969 if change_desc:
1970 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
1971 print '\nGot exception while uploading -- saving description to %s\n' \
1972 % backup_path
1973 backup_file = open(backup_path, 'w')
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001974 backup_file.write(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001975 backup_file.close()
1976 raise
1977
1978 if not cl.GetIssue():
1979 cl.SetIssue(issue)
1980 cl.SetPatchset(patchset)
maruel@chromium.org27bb3872011-05-30 20:33:19 +00001981
1982 if options.use_commit_queue:
1983 cl.SetFlag('commit', '1')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001984 return 0
1985
1986
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00001987def cleanup_list(l):
1988 """Fixes a list so that comma separated items are put as individual items.
1989
1990 So that "--reviewers joe@c,john@c --reviewers joa@c" results in
1991 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']).
1992 """
1993 items = sum((i.split(',') for i in l), [])
1994 stripped_items = (i.strip() for i in items)
1995 return sorted(filter(None, stripped_items))
1996
1997
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001998@subcommand.usage('[args to "git diff"]')
ukai@chromium.orge8077812012-02-03 03:41:46 +00001999def CMDupload(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002000 """Uploads the current changelist to codereview."""
ukai@chromium.orge8077812012-02-03 03:41:46 +00002001 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
2002 help='bypass upload presubmit hook')
brettw@chromium.orgb65c43c2013-06-10 22:04:49 +00002003 parser.add_option('--bypass-watchlists', action='store_true',
2004 dest='bypass_watchlists',
2005 help='bypass watchlists auto CC-ing reviewers')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002006 parser.add_option('-f', action='store_true', dest='force',
2007 help="force yes to questions (don't prompt)")
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00002008 parser.add_option('-m', dest='message', help='message for patchset')
2009 parser.add_option('-t', dest='title', help='title for patchset')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002010 parser.add_option('-r', '--reviewers',
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002011 action='append', default=[],
ukai@chromium.orge8077812012-02-03 03:41:46 +00002012 help='reviewer email addresses')
2013 parser.add_option('--cc',
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002014 action='append', default=[],
ukai@chromium.orge8077812012-02-03 03:41:46 +00002015 help='cc email addresses')
adamk@chromium.org36f47302013-04-05 01:08:31 +00002016 parser.add_option('-s', '--send-mail', action='store_true',
ukai@chromium.orge8077812012-02-03 03:41:46 +00002017 help='send email to reviewer immediately')
pgervais@chromium.orgb9f27512014-08-08 15:52:33 +00002018 parser.add_option('--emulate_svn_auto_props',
2019 '--emulate-svn-auto-props',
2020 action="store_true",
ukai@chromium.orge8077812012-02-03 03:41:46 +00002021 dest="emulate_svn_auto_props",
2022 help="Emulate Subversion's auto properties feature.")
ukai@chromium.orge8077812012-02-03 03:41:46 +00002023 parser.add_option('-c', '--use-commit-queue', action='store_true',
2024 help='tell the commit queue to commit this patchset')
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00002025 parser.add_option('--private', action='store_true',
2026 help='set the review private (rietveld only)')
ukai@chromium.org8ef7ab22012-11-28 04:24:52 +00002027 parser.add_option('--target_branch',
pgervais@chromium.orgb9f27512014-08-08 15:52:33 +00002028 '--target-branch',
wittman@chromium.org455dc922015-01-26 20:15:50 +00002029 metavar='TARGET',
2030 help='Apply CL to remote ref TARGET. ' +
2031 'Default: remote branch head, or master')
bauerb@chromium.org27386dd2015-02-16 10:45:39 +00002032 parser.add_option('--squash', action='store_true',
2033 help='Squash multiple commits into one (Gerrit only)')
pgervais@chromium.org91141372014-01-09 23:27:20 +00002034 parser.add_option('--email', default=None,
2035 help='email address to use to connect to Rietveld')
piman@chromium.org336f9122014-09-04 02:16:55 +00002036 parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true',
2037 help='add a set of OWNERS to TBR')
rmistry@google.comef966222015-04-07 11:15:01 +00002038 parser.add_option('--cq-dry-run', dest='cq_dry_run', action='store_true',
2039 help='Send the patchset to do a CQ dry run right after '
2040 'upload.')
pgervais@chromium.org91141372014-01-09 23:27:20 +00002041
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002042 add_git_similarity(parser)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002043 auth.add_auth_options(parser)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002044 (options, args) = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002045 auth_config = auth.extract_auth_config_from_options(options)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002046
sbc@chromium.org71437c02015-04-09 19:29:40 +00002047 if git_common.is_dirty_git_tree('upload'):
ukai@chromium.orge8077812012-02-03 03:41:46 +00002048 return 1
2049
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002050 options.reviewers = cleanup_list(options.reviewers)
2051 options.cc = cleanup_list(options.cc)
2052
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002053 cl = Changelist(auth_config=auth_config)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002054 if args:
2055 # TODO(ukai): is it ok for gerrit case?
2056 base_branch = args[0]
2057 else:
luqui@chromium.org64e14362015-01-07 00:29:29 +00002058 if cl.GetBranch() is None:
2059 DieWithError('Can\'t upload from detached HEAD state. Get on a branch!')
2060
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00002061 # Default to diffing against common ancestor of upstream branch
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002062 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org5e07e062013-02-28 23:55:44 +00002063 args = [base_branch, 'HEAD']
ukai@chromium.orge8077812012-02-03 03:41:46 +00002064
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002065 # Apply watchlists on upload.
2066 change = cl.GetChange(base_branch, None)
2067 watchlist = watchlists.Watchlists(change.RepositoryRoot())
2068 files = [f.LocalPath() for f in change.AffectedFiles()]
brettw@chromium.orgb65c43c2013-06-10 22:04:49 +00002069 if not options.bypass_watchlists:
2070 cl.SetWatchers(watchlist.GetWatchersForPaths(files))
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002071
ukai@chromium.orge8077812012-02-03 03:41:46 +00002072 if not options.bypass_hooks:
piman@chromium.org336f9122014-09-04 02:16:55 +00002073 if options.reviewers or options.tbr_owners:
isherman@chromium.orgb5cded62014-03-25 17:47:57 +00002074 # Set the reviewer list now so that presubmit checks can access it.
2075 change_description = ChangeDescription(change.FullDescriptionText())
piman@chromium.org336f9122014-09-04 02:16:55 +00002076 change_description.update_reviewers(options.reviewers,
2077 options.tbr_owners,
2078 change)
isherman@chromium.orgb5cded62014-03-25 17:47:57 +00002079 change.SetDescriptionText(change_description.description)
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002080 hook_results = cl.RunHook(committing=False,
ukai@chromium.orge8077812012-02-03 03:41:46 +00002081 may_prompt=not options.force,
2082 verbose=options.verbose,
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00002083 change=change)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002084 if not hook_results.should_continue():
2085 return 1
2086 if not options.reviewers and hook_results.reviewers:
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00002087 options.reviewers = hook_results.reviewers.split(',')
ukai@chromium.orge8077812012-02-03 03:41:46 +00002088
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002089 if cl.GetIssue():
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002090 latest_patchset = cl.GetMostRecentPatchset()
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002091 local_patchset = cl.GetPatchset()
dmikurube@chromium.org07d149f2013-04-03 11:40:23 +00002092 if latest_patchset and local_patchset and local_patchset != latest_patchset:
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002093 print ('The last upload made from this repository was patchset #%d but '
2094 'the most recent patchset on the server is #%d.'
2095 % (local_patchset, latest_patchset))
koz@chromium.orgc7192782013-04-09 23:28:46 +00002096 print ('Uploading will still work, but if you\'ve uploaded to this issue '
2097 'from another machine or branch the patch you\'re uploading now '
2098 'might not include those changes.')
koz@chromium.org5974d7a2013-04-02 20:50:37 +00002099 ask_for_data('About to upload; enter to confirm.')
2100
iannucci@chromium.org79540052012-10-19 23:15:26 +00002101 print_stats(options.similarity, options.find_copies, args)
ukai@chromium.orge8077812012-02-03 03:41:46 +00002102 if settings.GetIsGerrit():
piman@chromium.org336f9122014-09-04 02:16:55 +00002103 return GerritUpload(options, args, cl, change)
2104 ret = RietveldUpload(options, args, cl, change)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00002105 if not ret:
rogerta@chromium.org4a6cd042013-04-12 15:40:42 +00002106 git_set_branch_value('last-upload-hash',
2107 RunGit(['rev-parse', 'HEAD']).strip())
rmistry@google.com5626a922015-02-26 14:03:30 +00002108 # Run post upload hooks, if specified.
2109 if settings.GetRunPostUploadHook():
2110 presubmit_support.DoPostUploadExecuter(
2111 change,
2112 cl,
2113 settings.GetRoot(),
2114 options.verbose,
2115 sys.stdout)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00002116
2117 return ret
ukai@chromium.orge8077812012-02-03 03:41:46 +00002118
2119
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002120def IsSubmoduleMergeCommit(ref):
2121 # When submodules are added to the repo, we expect there to be a single
2122 # non-git-svn merge commit at remote HEAD with a signature comment.
2123 pattern = '^SVN changes up to revision [0-9]*$'
szager@chromium.orge84b7542012-06-15 21:26:58 +00002124 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref]
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002125 return RunGit(cmd) != ''
2126
2127
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002128def SendUpstream(parser, args, cmd):
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00002129 """Common code for CMDland and CmdDCommit
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002130
iannucci@chromium.org5724c962014-04-11 09:32:56 +00002131 Squashes branch into a single commit.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002132 Updates changelog with metadata (e.g. pointer to review).
2133 Pushes/dcommits the code upstream.
2134 Updates review and closes.
2135 """
2136 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
2137 help='bypass upload presubmit hook')
2138 parser.add_option('-m', dest='message',
2139 help="override review description")
2140 parser.add_option('-f', action='store_true', dest='force',
2141 help="force yes to questions (don't prompt)")
2142 parser.add_option('-c', dest='contributor',
2143 help="external contributor for patch (appended to " +
2144 "description and used as author for git). Should be " +
2145 "formatted as 'First Last <email@example.com>'")
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00002146 add_git_similarity(parser)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002147 auth.add_auth_options(parser)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002148 (options, args) = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002149 auth_config = auth.extract_auth_config_from_options(options)
2150
2151 cl = Changelist(auth_config=auth_config)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002152
iannucci@chromium.org5724c962014-04-11 09:32:56 +00002153 current = cl.GetBranch()
2154 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
2155 if not settings.GetIsGitSvn() and remote == '.':
2156 print
2157 print 'Attempting to push branch %r into another local branch!' % current
2158 print
2159 print 'Either reparent this branch on top of origin/master:'
2160 print ' git reparent-branch --root'
2161 print
2162 print 'OR run `git rebase-update` if you think the parent branch is already'
2163 print 'committed.'
2164 print
2165 print ' Current parent: %r' % upstream_branch
2166 return 1
2167
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002168 if not args or cmd == 'land':
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002169 # Default to merging against our best guess of the upstream branch.
2170 args = [cl.GetUpstreamBranch()]
2171
maruel@chromium.org13f623c2011-07-22 16:02:23 +00002172 if options.contributor:
2173 if not re.match('^.*\s<\S+@\S+>$', options.contributor):
2174 print "Please provide contibutor as 'First Last <email@example.com>'"
2175 return 1
2176
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002177 base_branch = args[0]
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002178 base_has_submodules = IsSubmoduleMergeCommit(base_branch)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002179
sbc@chromium.org71437c02015-04-09 19:29:40 +00002180 if git_common.is_dirty_git_tree(cmd):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002181 return 1
2182
2183 # This rev-list syntax means "show all commits not in my branch that
2184 # are in base_branch".
2185 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(),
2186 base_branch]).splitlines()
2187 if upstream_commits:
2188 print ('Base branch "%s" has %d commits '
2189 'not in this branch.' % (base_branch, len(upstream_commits)))
2190 print 'Run "git merge %s" before attempting to %s.' % (base_branch, cmd)
2191 return 1
2192
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002193 # This is the revision `svn dcommit` will commit on top of.
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002194 svn_head = None
2195 if cmd == 'dcommit' or base_has_submodules:
2196 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1',
2197 '--pretty=format:%H'])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002198
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002199 if cmd == 'dcommit':
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002200 # If the base_head is a submodule merge commit, the first parent of the
2201 # base_head should be a git-svn commit, which is what we're interested in.
2202 base_svn_head = base_branch
2203 if base_has_submodules:
2204 base_svn_head += '^1'
2205
2206 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002207 if extra_commits:
2208 print ('This branch has %d additional commits not upstreamed yet.'
2209 % len(extra_commits.splitlines()))
2210 print ('Upstream "%s" or rebase this branch on top of the upstream trunk '
2211 'before attempting to %s.' % (base_branch, cmd))
2212 return 1
2213
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002214 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip()
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002215 if not options.bypass_hooks:
maruel@chromium.org13f623c2011-07-22 16:02:23 +00002216 author = None
2217 if options.contributor:
2218 author = re.search(r'\<(.*)\>', options.contributor).group(1)
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002219 hook_results = cl.RunHook(
2220 committing=True,
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002221 may_prompt=not options.force,
2222 verbose=options.verbose,
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002223 change=cl.GetChange(merge_base, author))
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00002224 if not hook_results.should_continue():
2225 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002226
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002227 # Check the tree status if the tree status URL is set.
2228 status = GetTreeStatus()
2229 if 'closed' == status:
2230 print('The tree is closed. Please wait for it to reopen. Use '
2231 '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
2232 return 1
2233 elif 'unknown' == status:
2234 print('Unable to determine tree status. Please verify manually and '
2235 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
2236 return 1
maruel@chromium.orgac637152012-01-16 14:19:54 +00002237 else:
2238 breakpad.SendStack(
2239 'GitClHooksBypassedCommit',
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002240 'Issue %s/%s bypassed hook when committing (tree status was "%s")' %
2241 (cl.GetRietveldServer(), cl.GetIssue(), GetTreeStatus()),
maruel@chromium.org2e72bb12012-01-17 15:18:35 +00002242 verbose=False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002243
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002244 change_desc = ChangeDescription(options.message)
2245 if not change_desc.description and cl.GetIssue():
2246 change_desc = ChangeDescription(cl.GetDescription())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002247
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002248 if not change_desc.description:
erg@chromium.org1a173982012-08-29 20:43:05 +00002249 if not cl.GetIssue() and options.bypass_hooks:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002250 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base]))
erg@chromium.org1a173982012-08-29 20:43:05 +00002251 else:
2252 print 'No description set.'
2253 print 'Visit %s/edit to set it.' % (cl.GetIssueURL())
2254 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002255
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002256 # Keep a separate copy for the commit message, because the commit message
2257 # contains the link to the Rietveld issue, while the Rietveld message contains
2258 # the commit viewvc url.
maruel@chromium.orge52678e2013-04-26 18:34:44 +00002259 # Keep a separate copy for the commit message.
2260 if cl.GetIssue():
maruel@chromium.orgcf087782013-07-23 13:08:48 +00002261 change_desc.update_reviewers(cl.GetApprovingReviewers())
maruel@chromium.orge52678e2013-04-26 18:34:44 +00002262
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002263 commit_desc = ChangeDescription(change_desc.description)
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00002264 if cl.GetIssue():
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002265 commit_desc.append_footer('Review URL: %s' % cl.GetIssueURL())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002266 if options.contributor:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002267 commit_desc.append_footer('Patch from %s.' % options.contributor)
2268
agable@chromium.orgeec3ea32013-08-15 20:31:39 +00002269 print('Description:')
2270 print(commit_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002271
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002272 branches = [merge_base, cl.GetBranchRef()]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002273 if not options.force:
iannucci@chromium.org79540052012-10-19 23:15:26 +00002274 print_stats(options.similarity, options.find_copies, branches)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002275
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002276 # We want to squash all this branch's commits into one commit with the proper
2277 # description. We do this by doing a "reset --soft" to the base branch (which
2278 # keeps the working copy the same), then dcommitting that. If origin/master
2279 # has a submodule merge commit, we'll also need to cherry-pick the squashed
2280 # commit onto a branch based on the git-svn head.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002281 MERGE_BRANCH = 'git-cl-commit'
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002282 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
2283 # Delete the branches if they exist.
2284 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]:
2285 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch]
2286 result = RunGitWithCode(showref_cmd)
2287 if result[0] == 0:
2288 RunGit(['branch', '-D', branch])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002289
2290 # We might be in a directory that's present in this branch but not in the
2291 # trunk. Move up to the top of the tree so that git commands that expect a
2292 # valid CWD won't fail after we check out the merge branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002293 rel_base_path = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002294 if rel_base_path:
2295 os.chdir(rel_base_path)
2296
2297 # Stuff our change into the merge branch.
2298 # We wrap in a try...finally block so if anything goes wrong,
2299 # we clean up the branches.
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002300 retcode = -1
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002301 pushed_to_pending = False
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002302 pending_ref = None
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002303 revision = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002304 try:
bauerb@chromium.orgb4a75c42011-03-08 08:35:38 +00002305 RunGit(['checkout', '-q', '-b', MERGE_BRANCH])
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002306 RunGit(['reset', '--soft', merge_base])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002307 if options.contributor:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002308 RunGit(
2309 [
2310 'commit', '--author', options.contributor,
2311 '-m', commit_desc.description,
2312 ])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002313 else:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002314 RunGit(['commit', '-m', commit_desc.description])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002315 if base_has_submodules:
2316 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip()
2317 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head])
2318 RunGit(['checkout', CHERRY_PICK_BRANCH])
2319 RunGit(['cherry-pick', cherry_pick_commit])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002320 if cmd == 'land':
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00002321 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002322 pending_prefix = settings.GetPendingRefPrefix()
2323 if not pending_prefix or branch.startswith(pending_prefix):
2324 # If not using refs/pending/heads/* at all, or target ref is already set
2325 # to pending, then push to the target ref directly.
2326 retcode, output = RunGitWithCode(
2327 ['push', '--porcelain', remote, 'HEAD:%s' % branch])
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002328 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002329 else:
2330 # Cherry-pick the change on top of pending ref and then push it.
2331 assert branch.startswith('refs/'), branch
2332 assert pending_prefix[-1] == '/', pending_prefix
2333 pending_ref = pending_prefix + branch[len('refs/'):]
2334 retcode, output = PushToGitPending(remote, pending_ref, branch)
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002335 pushed_to_pending = (retcode == 0)
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002336 if retcode == 0:
2337 revision = RunGit(['rev-parse', 'HEAD']).strip()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002338 else:
2339 # dcommit the merge branch.
iannucci@chromium.orga1950c42014-12-05 22:15:56 +00002340 cmd_args = [
kjellander@chromium.org6abc6522014-12-02 07:34:49 +00002341 'svn', 'dcommit',
2342 '-C%s' % options.similarity,
2343 '--no-rebase', '--rmdir',
2344 ]
2345 if settings.GetForceHttpsCommitUrl():
2346 # Allow forcing https commit URLs for some projects that don't allow
2347 # committing to http URLs (like Google Code).
2348 remote_url = cl.GetGitSvnRemoteUrl()
2349 if urlparse.urlparse(remote_url).scheme == 'http':
2350 remote_url = remote_url.replace('http://', 'https://')
iannucci@chromium.orga1950c42014-12-05 22:15:56 +00002351 cmd_args.append('--commit-url=%s' % remote_url)
2352 _, output = RunGitWithCode(cmd_args)
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002353 if 'Committed r' in output:
2354 revision = re.match(
2355 '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1)
2356 logging.debug(output)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002357 finally:
2358 # And then swap back to the original branch and clean up.
2359 RunGit(['checkout', '-q', cl.GetBranch()])
2360 RunGit(['branch', '-D', MERGE_BRANCH])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002361 if base_has_submodules:
2362 RunGit(['branch', '-D', CHERRY_PICK_BRANCH])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002363
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002364 if not revision:
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002365 print 'Failed to push. If this persists, please file a bug.'
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002366 return 1
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002367
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002368 killed = False
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002369 if pushed_to_pending:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002370 try:
2371 revision = WaitForRealCommit(remote, revision, base_branch, branch)
2372 # We set pushed_to_pending to False, since it made it all the way to the
2373 # real ref.
2374 pushed_to_pending = False
2375 except KeyboardInterrupt:
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002376 killed = True
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002377
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002378 if cl.GetIssue():
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002379 to_pending = ' to pending queue' if pushed_to_pending else ''
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002380 viewvc_url = settings.GetViewVCUrl()
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002381 if not to_pending:
2382 if viewvc_url and revision:
2383 change_desc.append_footer(
2384 'Committed: %s%s' % (viewvc_url, revision))
2385 elif revision:
2386 change_desc.append_footer('Committed: %s' % (revision,))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002387 print ('Closing issue '
2388 '(you may be prompted for your codereview password)...')
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002389 cl.UpdateDescription(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002390 cl.CloseIssue()
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002391 props = cl.GetIssueProperties()
sadrul@chromium.org34b5d822013-02-18 01:39:24 +00002392 patch_num = len(props['patchsets'])
rmistry@google.com52d224a2014-08-27 14:44:41 +00002393 comment = "Committed patchset #%d (id:%d)%s manually as %s" % (
mark@chromium.org782570c2014-09-26 21:48:02 +00002394 patch_num, props['patchsets'][-1], to_pending, revision)
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002395 if options.bypass_hooks:
2396 comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.'
2397 else:
2398 comment += ' (presubmit successful).'
iannucci@chromium.orgb85a3162013-01-26 01:11:13 +00002399 cl.RpcServer().add_comment(cl.GetIssue(), comment)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002400 cl.SetIssue(None)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002401
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002402 if pushed_to_pending:
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002403 _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
2404 print 'The commit is in the pending queue (%s).' % pending_ref
2405 print (
thakis@chromium.org5f32a962014-09-05 21:33:23 +00002406 'It will show up on %s in ~1 min, once it gets a Cr-Commit-Position '
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002407 'footer.' % branch)
2408
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002409 hook = POSTUPSTREAM_HOOK_PATTERN % cmd
2410 if os.path.isfile(hook):
2411 RunCommand([hook, merge_base], error_ok=True)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002412
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002413 return 1 if killed else 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002414
2415
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002416def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
2417 print
2418 print 'Waiting for commit to be landed on %s...' % real_ref
2419 print '(If you are impatient, you may Ctrl-C once without harm)'
2420 target_tree = RunGit(['rev-parse', '%s:' % pushed_commit]).strip()
2421 current_rev = RunGit(['rev-parse', local_base_ref]).strip()
2422
2423 loop = 0
2424 while True:
2425 sys.stdout.write('fetching (%d)... \r' % loop)
2426 sys.stdout.flush()
2427 loop += 1
2428
2429 RunGit(['retry', 'fetch', remote, real_ref], stderr=subprocess2.VOID)
2430 to_rev = RunGit(['rev-parse', 'FETCH_HEAD']).strip()
2431 commits = RunGit(['rev-list', '%s..%s' % (current_rev, to_rev)])
2432 for commit in commits.splitlines():
2433 if RunGit(['rev-parse', '%s:' % commit]).strip() == target_tree:
2434 print 'Found commit on %s' % real_ref
2435 return commit
2436
2437 current_rev = to_rev
2438
2439
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002440def PushToGitPending(remote, pending_ref, upstream_ref):
2441 """Fetches pending_ref, cherry-picks current HEAD on top of it, pushes.
2442
2443 Returns:
2444 (retcode of last operation, output log of last operation).
2445 """
2446 assert pending_ref.startswith('refs/'), pending_ref
2447 local_pending_ref = 'refs/git-cl/' + pending_ref[len('refs/'):]
2448 cherry = RunGit(['rev-parse', 'HEAD']).strip()
2449 code = 0
2450 out = ''
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002451 max_attempts = 3
2452 attempts_left = max_attempts
2453 while attempts_left:
2454 if attempts_left != max_attempts:
2455 print 'Retrying, %d attempts left...' % (attempts_left - 1,)
2456 attempts_left -= 1
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002457
2458 # Fetch. Retry fetch errors.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002459 print 'Fetching pending ref %s...' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002460 code, out = RunGitWithCode(
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002461 ['retry', 'fetch', remote, '+%s:%s' % (pending_ref, local_pending_ref)])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002462 if code:
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002463 print 'Fetch failed with exit code %d.' % code
2464 if out.strip():
2465 print out.strip()
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002466 continue
2467
2468 # Try to cherry pick. Abort on merge conflicts.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002469 print 'Cherry-picking commit on top of pending ref...'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002470 RunGitWithCode(['checkout', local_pending_ref], suppress_stderr=True)
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002471 code, out = RunGitWithCode(['cherry-pick', cherry])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002472 if code:
2473 print (
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002474 'Your patch doesn\'t apply cleanly to ref \'%s\', '
2475 'the following files have merge conflicts:' % pending_ref)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002476 print RunGit(['diff', '--name-status', '--diff-filter=U']).strip()
2477 print 'Please rebase your patch and try again.'
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002478 RunGitWithCode(['cherry-pick', '--abort'])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002479 return code, out
2480
2481 # Applied cleanly, try to push now. Retry on error (flake or non-ff push).
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002482 print 'Pushing commit to %s... It can take a while.' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002483 code, out = RunGitWithCode(
2484 ['retry', 'push', '--porcelain', remote, 'HEAD:%s' % pending_ref])
2485 if code == 0:
2486 # Success.
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002487 print 'Commit pushed to pending ref successfully!'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002488 return code, out
2489
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002490 print 'Push failed with exit code %d.' % code
2491 if out.strip():
2492 print out.strip()
2493 if IsFatalPushFailure(out):
2494 print (
2495 'Fatal push error. Make sure your .netrc credentials and git '
2496 'user.email are correct and you have push access to the repo.')
2497 return code, out
2498
2499 print 'All attempts to push to pending ref failed.'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002500 return code, out
2501
2502
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002503def IsFatalPushFailure(push_stdout):
2504 """True if retrying push won't help."""
2505 return '(prohibited by Gerrit)' in push_stdout
2506
2507
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002508@subcommand.usage('[upstream branch to apply against]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002509def CMDdcommit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002510 """Commits the current changelist via git-svn."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002511 if not settings.GetIsGitSvn():
thakis@chromium.orgcde3bb62011-01-20 01:16:14 +00002512 message = """This doesn't appear to be an SVN repository.
2513If your project has a git mirror with an upstream SVN master, you probably need
2514to run 'git svn init', see your project's git mirror documentation.
2515If your project has a true writeable upstream repository, you probably want
mark@chromium.org671c7a32014-07-31 17:09:36 +00002516to run 'git cl land' instead.
thakis@chromium.orgcde3bb62011-01-20 01:16:14 +00002517Choose wisely, if you get this wrong, your commit might appear to succeed but
2518will instead be silently ignored."""
2519 print(message)
maruel@chromium.org90541732011-04-01 17:54:18 +00002520 ask_for_data('[Press enter to dcommit or ctrl-C to quit]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002521 return SendUpstream(parser, args, 'dcommit')
2522
2523
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002524@subcommand.usage('[upstream branch to apply against]')
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00002525def CMDland(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002526 """Commits the current changelist via git."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002527 if settings.GetIsGitSvn():
2528 print('This appears to be an SVN repository.')
2529 print('Are you sure you didn\'t mean \'git cl dcommit\'?')
maruel@chromium.org90541732011-04-01 17:54:18 +00002530 ask_for_data('[Press enter to push or ctrl-C to quit]')
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002531 return SendUpstream(parser, args, 'land')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002532
2533
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002534@subcommand.usage('<patch url or issue id>')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002535def CMDpatch(parser, args):
marq@chromium.orge5e59002013-10-02 23:21:25 +00002536 """Patches in a code review."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002537 parser.add_option('-b', dest='newbranch',
2538 help='create a new branch off trunk for the patch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002539 parser.add_option('-f', '--force', action='store_true',
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002540 help='with -b, clobber any existing branch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002541 parser.add_option('-d', '--directory', action='store', metavar='DIR',
2542 help='Change to the directory DIR immediately, '
2543 'before doing anything else.')
2544 parser.add_option('--reject', action='store_true',
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00002545 help='failed patches spew .rej files rather than '
2546 'attempting a 3-way merge')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002547 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit',
2548 help="don't commit after patch applies")
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002549 auth.add_auth_options(parser)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002550 (options, args) = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002551 auth_config = auth.extract_auth_config_from_options(options)
2552
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002553 if len(args) != 1:
2554 parser.print_help()
2555 return 1
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002556 issue_arg = args[0]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002557
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002558 # We don't want uncommitted changes mixed up with the patch.
sbc@chromium.org71437c02015-04-09 19:29:40 +00002559 if git_common.is_dirty_git_tree('patch'):
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002560 return 1
2561
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002562 # TODO(maruel): Use apply_issue.py
ukai@chromium.orge8077812012-02-03 03:41:46 +00002563 # TODO(ukai): use gerrit-cherry-pick for gerrit repository?
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002564
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002565 if options.newbranch:
2566 if options.force:
2567 RunGit(['branch', '-D', options.newbranch],
2568 stderr=subprocess2.PIPE, error_ok=True)
2569 RunGit(['checkout', '-b', options.newbranch,
2570 Changelist().GetUpstreamBranch()])
2571
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002572 return PatchIssue(issue_arg, options.reject, options.nocommit,
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002573 options.directory, auth_config)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002574
2575
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002576def PatchIssue(issue_arg, reject, nocommit, directory, auth_config):
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002577 # There's a "reset --hard" when failing to apply the patch. In order
2578 # not to destroy users' data, make sure the tree is not dirty here.
sbc@chromium.org71437c02015-04-09 19:29:40 +00002579 assert(not git_common.is_dirty_git_tree('apply'))
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002580
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002581 if type(issue_arg) is int or issue_arg.isdigit():
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002582 # Input is an issue id. Figure out the URL.
maruel@chromium.org52424302012-08-29 15:14:30 +00002583 issue = int(issue_arg)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002584 cl = Changelist(issue=issue, auth_config=auth_config)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002585 patchset = cl.GetMostRecentPatchset()
binji@chromium.org0281f522012-09-14 13:37:59 +00002586 patch_data = cl.GetPatchSetDiff(issue, patchset)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002587 else:
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00002588 # Assume it's a URL to the patch. Default to https.
2589 issue_url = gclient_utils.UpgradeToHttps(issue_arg)
binji@chromium.org0281f522012-09-14 13:37:59 +00002590 match = re.match(r'.*?/issue(\d+)_(\d+).diff', issue_url)
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002591 if not match:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002592 DieWithError('Must pass an issue ID or full URL for '
2593 '\'Download raw patch set\'')
maruel@chromium.org52424302012-08-29 15:14:30 +00002594 issue = int(match.group(1))
binji@chromium.org0281f522012-09-14 13:37:59 +00002595 patchset = int(match.group(2))
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002596 patch_data = urllib2.urlopen(issue_arg).read()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002597
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002598 # Switch up to the top-level directory, if necessary, in preparation for
2599 # applying the patch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002600 top = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002601 if top:
2602 os.chdir(top)
2603
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002604 # Git patches have a/ at the beginning of source paths. We strip that out
2605 # with a sed script rather than the -p flag to patch so we can feed either
2606 # Git or svn-style patches into the same apply command.
2607 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002608 try:
2609 patch_data = subprocess2.check_output(
2610 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
2611 except subprocess2.CalledProcessError:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002612 DieWithError('Git patch mungling failed.')
2613 logging.info(patch_data)
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002614
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002615 # We use "git apply" to apply the patch instead of "patch" so that we can
2616 # pick up file adds.
2617 # The --index flag means: also insert into the index (so we catch adds).
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002618 cmd = ['git', 'apply', '--index', '-p0']
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002619 if directory:
2620 cmd.extend(('--directory', directory))
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002621 if reject:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002622 cmd.append('--reject')
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00002623 elif IsGitVersionAtLeast('1.7.12'):
2624 cmd.append('--3way')
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002625 try:
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002626 subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002627 stdin=patch_data, stdout=subprocess2.VOID)
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002628 except subprocess2.CalledProcessError:
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002629 RunGit(['reset', '--hard'])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002630 DieWithError('Failed to apply the patch')
2631
2632 # If we had an issue, commit the current state and register the issue.
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002633 if not nocommit:
carlosk@chromium.org71284d92014-11-14 18:12:50 +00002634 RunGit(['commit', '-m', ('patch from issue %(i)s at patchset '
2635 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)'
2636 % {'i': issue, 'p': patchset})])
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002637 cl = Changelist(auth_config=auth_config)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002638 cl.SetIssue(issue)
binji@chromium.org0281f522012-09-14 13:37:59 +00002639 cl.SetPatchset(patchset)
pdr@chromium.org98ca6622013-04-09 20:58:40 +00002640 print "Committed patch locally."
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002641 else:
2642 print "Patch applied to index."
2643 return 0
2644
2645
2646def CMDrebase(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002647 """Rebases current branch on top of svn repo."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002648 # Provide a wrapper for git svn rebase to help avoid accidental
2649 # git svn dcommit.
2650 # It's the only command that doesn't use parser at all since we just defer
2651 # execution to git-svn.
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002652
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002653 return RunGitWithCode(['svn', 'rebase'] + args)[1]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002654
2655
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002656def GetTreeStatus(url=None):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002657 """Fetches the tree status and returns either 'open', 'closed',
2658 'unknown' or 'unset'."""
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002659 url = url or settings.GetTreeStatusUrl(error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002660 if url:
2661 status = urllib2.urlopen(url).read().lower()
2662 if status.find('closed') != -1 or status == '0':
2663 return 'closed'
2664 elif status.find('open') != -1 or status == '1':
2665 return 'open'
2666 return 'unknown'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002667 return 'unset'
2668
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002669
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002670def GetTreeStatusReason():
2671 """Fetches the tree status from a json url and returns the message
2672 with the reason for the tree to be opened or closed."""
msb@chromium.orgbf1a7ba2011-02-01 16:21:46 +00002673 url = settings.GetTreeStatusUrl()
2674 json_url = urlparse.urljoin(url, '/current?format=json')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002675 connection = urllib2.urlopen(json_url)
2676 status = json.loads(connection.read())
2677 connection.close()
2678 return status['message']
2679
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002680
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002681def GetBuilderMaster(bot_list):
2682 """For a given builder, fetch the master from AE if available."""
2683 map_url = 'https://builders-map.appspot.com/'
2684 try:
2685 master_map = json.load(urllib2.urlopen(map_url))
2686 except urllib2.URLError as e:
2687 return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' %
2688 (map_url, e))
2689 except ValueError as e:
2690 return None, ('Invalid json string from %s. Error: %s.' % (map_url, e))
2691 if not master_map:
2692 return None, 'Failed to build master map.'
2693
2694 result_master = ''
2695 for bot in bot_list:
2696 builder = bot.split(':', 1)[0]
2697 master_list = master_map.get(builder, [])
2698 if not master_list:
2699 return None, ('No matching master for builder %s.' % builder)
2700 elif len(master_list) > 1:
2701 return None, ('The builder name %s exists in multiple masters %s.' %
2702 (builder, master_list))
2703 else:
2704 cur_master = master_list[0]
2705 if not result_master:
2706 result_master = cur_master
2707 elif result_master != cur_master:
2708 return None, 'The builders do not belong to the same master.'
2709 return result_master, None
2710
2711
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002712def CMDtree(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002713 """Shows the status of the tree."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002714 _, args = parser.parse_args(args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002715 status = GetTreeStatus()
2716 if 'unset' == status:
2717 print 'You must configure your tree status URL by running "git cl config".'
2718 return 2
2719
2720 print "The tree is %s" % status
2721 print
2722 print GetTreeStatusReason()
2723 if status != 'open':
2724 return 1
2725 return 0
2726
2727
maruel@chromium.org15192402012-09-06 12:38:29 +00002728def CMDtry(parser, args):
2729 """Triggers a try job through Rietveld."""
2730 group = optparse.OptionGroup(parser, "Try job options")
2731 group.add_option(
2732 "-b", "--bot", action="append",
2733 help=("IMPORTANT: specify ONE builder per --bot flag. Use it multiple "
2734 "times to specify multiple builders. ex: "
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00002735 "'-b win_rel -b win_layout'. See "
maruel@chromium.org15192402012-09-06 12:38:29 +00002736 "the try server waterfall for the builders name and the tests "
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00002737 "available."))
maruel@chromium.org15192402012-09-06 12:38:29 +00002738 group.add_option(
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002739 "-m", "--master", default='',
iannucci@chromium.org9e849272014-04-04 00:31:55 +00002740 help=("Specify a try master where to run the tries."))
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002741 group.add_option(
maruel@chromium.org15192402012-09-06 12:38:29 +00002742 "-r", "--revision",
2743 help="Revision to use for the try job; default: the "
2744 "revision will be determined by the try server; see "
2745 "its waterfall for more info")
2746 group.add_option(
2747 "-c", "--clobber", action="store_true", default=False,
2748 help="Force a clobber before building; e.g. don't do an "
2749 "incremental build")
2750 group.add_option(
2751 "--project",
2752 help="Override which project to use. Projects are defined "
2753 "server-side to define what default bot set to use")
2754 group.add_option(
maruel@chromium.org15192402012-09-06 12:38:29 +00002755 "-n", "--name", help="Try job name; default to current branch name")
2756 parser.add_option_group(group)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002757 auth.add_auth_options(parser)
maruel@chromium.org15192402012-09-06 12:38:29 +00002758 options, args = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002759 auth_config = auth.extract_auth_config_from_options(options)
maruel@chromium.org15192402012-09-06 12:38:29 +00002760
2761 if args:
2762 parser.error('Unknown arguments: %s' % args)
2763
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002764 cl = Changelist(auth_config=auth_config)
maruel@chromium.org15192402012-09-06 12:38:29 +00002765 if not cl.GetIssue():
2766 parser.error('Need to upload first')
2767
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002768 props = cl.GetIssueProperties()
agable@chromium.org787e3062014-08-20 16:31:19 +00002769 if props.get('closed'):
2770 parser.error('Cannot send tryjobs for a closed CL')
2771
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002772 if props.get('private'):
2773 parser.error('Cannot use trybots with private issue')
2774
maruel@chromium.org15192402012-09-06 12:38:29 +00002775 if not options.name:
2776 options.name = cl.GetBranch()
2777
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00002778 if options.bot and not options.master:
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002779 options.master, err_msg = GetBuilderMaster(options.bot)
2780 if err_msg:
2781 parser.error('Tryserver master cannot be found because: %s\n'
2782 'Please manually specify the tryserver master'
2783 ', e.g. "-m tryserver.chromium.linux".' % err_msg)
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00002784
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002785 def GetMasterMap():
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00002786 # Process --bot.
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002787 if not options.bot:
2788 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
maruel@chromium.org15192402012-09-06 12:38:29 +00002789
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002790 # Get try masters from PRESUBMIT.py files.
2791 masters = presubmit_support.DoGetTryMasters(
2792 change,
2793 change.LocalPaths(),
2794 settings.GetRoot(),
2795 None,
2796 None,
2797 options.verbose,
2798 sys.stdout)
2799 if masters:
2800 return masters
stip@chromium.org43064fd2013-12-18 20:07:44 +00002801
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002802 # Fall back to deprecated method: get try slaves from PRESUBMIT.py files.
2803 options.bot = presubmit_support.DoGetTrySlaves(
2804 change,
2805 change.LocalPaths(),
2806 settings.GetRoot(),
2807 None,
2808 None,
2809 options.verbose,
2810 sys.stdout)
2811 if not options.bot:
2812 parser.error('No default try builder to try, use --bot')
maruel@chromium.org15192402012-09-06 12:38:29 +00002813
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002814 builders_and_tests = {}
2815 # TODO(machenbach): The old style command-line options don't support
2816 # multiple try masters yet.
2817 old_style = filter(lambda x: isinstance(x, basestring), options.bot)
2818 new_style = filter(lambda x: isinstance(x, tuple), options.bot)
2819
2820 for bot in old_style:
2821 if ':' in bot:
phajdan.jr@chromium.org52914132015-01-22 10:37:09 +00002822 parser.error('Specifying testfilter is no longer supported')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002823 elif ',' in bot:
2824 parser.error('Specify one bot per --bot flag')
2825 else:
2826 builders_and_tests.setdefault(bot, []).append('defaulttests')
2827
2828 for bot, tests in new_style:
2829 builders_and_tests.setdefault(bot, []).extend(tests)
2830
2831 # Return a master map with one master to be backwards compatible. The
2832 # master name defaults to an empty string, which will cause the master
2833 # not to be set on rietveld (deprecated).
2834 return {options.master: builders_and_tests}
2835
2836 masters = GetMasterMap()
stip@chromium.org43064fd2013-12-18 20:07:44 +00002837
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002838 for builders in masters.itervalues():
2839 if any('triggered' in b for b in builders):
2840 print >> sys.stderr, (
2841 'ERROR You are trying to send a job to a triggered bot. This type of'
2842 ' bot requires an\ninitial job from a parent (usually a builder). '
2843 'Instead send your job to the parent.\n'
2844 'Bot list: %s' % builders)
2845 return 1
ilevy@chromium.orgf3b21232012-09-24 20:48:55 +00002846
ilevy@chromium.org36e420b2013-08-06 23:21:12 +00002847 patchset = cl.GetMostRecentPatchset()
2848 if patchset and patchset != cl.GetPatchset():
2849 print(
2850 '\nWARNING Mismatch between local config and server. Did a previous '
2851 'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
2852 'Continuing using\npatchset %s.\n' % patchset)
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002853 try:
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002854 cl.RpcServer().trigger_distributed_try_jobs(
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002855 cl.GetIssue(), patchset, options.name, options.clobber,
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002856 options.revision, masters)
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002857 except urllib2.HTTPError, e:
2858 if e.code == 404:
2859 print('404 from rietveld; '
2860 'did you mean to use "git try" instead of "git cl try"?')
2861 return 1
maruel@chromium.org072d94b2012-09-20 19:20:08 +00002862 print('Tried jobs on:')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002863
2864 for (master, builders) in masters.iteritems():
2865 if master:
2866 print 'Master: %s' % master
2867 length = max(len(builder) for builder in builders)
2868 for builder in sorted(builders):
2869 print ' %*s: %s' % (length, builder, ','.join(builders[builder]))
maruel@chromium.org15192402012-09-06 12:38:29 +00002870 return 0
2871
2872
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002873@subcommand.usage('[new upstream branch]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002874def CMDupstream(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002875 """Prints or sets the name of the upstream branch, if any."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002876 _, args = parser.parse_args(args)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002877 if len(args) > 1:
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002878 parser.error('Unrecognized args: %s' % ' '.join(args))
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002879
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002880 cl = Changelist()
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002881 if args:
2882 # One arg means set upstream branch.
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00002883 branch = cl.GetBranch()
2884 RunGit(['branch', '--set-upstream', branch, args[0]])
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002885 cl = Changelist()
2886 print "Upstream branch set to " + cl.GetUpstreamBranch()
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00002887
2888 # Clear configured merge-base, if there is one.
2889 git_common.remove_merge_base(branch)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002890 else:
2891 print cl.GetUpstreamBranch()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002892 return 0
2893
2894
thestig@chromium.org00858c82013-12-02 23:08:03 +00002895def CMDweb(parser, args):
2896 """Opens the current CL in the web browser."""
2897 _, args = parser.parse_args(args)
2898 if args:
2899 parser.error('Unrecognized args: %s' % ' '.join(args))
2900
2901 issue_url = Changelist().GetIssueURL()
2902 if not issue_url:
2903 print >> sys.stderr, 'ERROR No issue to open'
2904 return 1
2905
2906 webbrowser.open(issue_url)
2907 return 0
2908
2909
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002910def CMDset_commit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002911 """Sets the commit bit to trigger the Commit Queue."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002912 auth.add_auth_options(parser)
2913 options, args = parser.parse_args(args)
2914 auth_config = auth.extract_auth_config_from_options(options)
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002915 if args:
2916 parser.error('Unrecognized args: %s' % ' '.join(args))
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002917 cl = Changelist(auth_config=auth_config)
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002918 props = cl.GetIssueProperties()
2919 if props.get('private'):
2920 parser.error('Cannot set commit on private issue')
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002921 cl.SetFlag('commit', '1')
2922 return 0
2923
2924
groby@chromium.org411034a2013-02-26 15:12:01 +00002925def CMDset_close(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002926 """Closes the issue."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002927 auth.add_auth_options(parser)
2928 options, args = parser.parse_args(args)
2929 auth_config = auth.extract_auth_config_from_options(options)
groby@chromium.org411034a2013-02-26 15:12:01 +00002930 if args:
2931 parser.error('Unrecognized args: %s' % ' '.join(args))
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002932 cl = Changelist(auth_config=auth_config)
groby@chromium.org411034a2013-02-26 15:12:01 +00002933 # Ensure there actually is an issue to close.
2934 cl.GetDescription()
2935 cl.CloseIssue()
2936 return 0
2937
2938
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002939def CMDdiff(parser, args):
wychen@chromium.org37b2ec02015-04-03 00:49:15 +00002940 """Shows differences between local tree and last upload."""
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002941 auth.add_auth_options(parser)
2942 options, args = parser.parse_args(args)
2943 auth_config = auth.extract_auth_config_from_options(options)
2944 if args:
2945 parser.error('Unrecognized args: %s' % ' '.join(args))
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002946
2947 # Uncommitted (staged and unstaged) changes will be destroyed by
2948 # "git reset --hard" if there are merging conflicts in PatchIssue().
2949 # Staged changes would be committed along with the patch from last
2950 # upload, hence counted toward the "last upload" side in the final
2951 # diff output, and this is not what we want.
sbc@chromium.org71437c02015-04-09 19:29:40 +00002952 if git_common.is_dirty_git_tree('diff'):
wychen@chromium.org46309bf2015-04-03 21:04:49 +00002953 return 1
2954
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002955 cl = Changelist(auth_config=auth_config)
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002956 issue = cl.GetIssue()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002957 branch = cl.GetBranch()
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002958 if not issue:
2959 DieWithError('No issue found for current branch (%s)' % branch)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002960 TMP_BRANCH = 'git-cl-diff'
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002961 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002962
2963 # Create a new branch based on the merge-base
2964 RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch])
2965 try:
2966 # Patch in the latest changes from rietveld.
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002967 rtn = PatchIssue(issue, False, False, None, auth_config)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002968 if rtn != 0:
2969 return rtn
2970
wychen@chromium.org06928532015-02-03 02:11:29 +00002971 # Switch back to starting branch and diff against the temporary
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002972 # branch containing the latest rietveld patch.
wychen@chromium.org06928532015-02-03 02:11:29 +00002973 subprocess2.check_call(['git', 'diff', TMP_BRANCH, branch, '--'])
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002974 finally:
2975 RunGit(['checkout', '-q', branch])
2976 RunGit(['branch', '-D', TMP_BRANCH])
2977
2978 return 0
2979
2980
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002981def CMDowners(parser, args):
wychen@chromium.org37b2ec02015-04-03 00:49:15 +00002982 """Interactively find the owners for reviewing."""
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002983 parser.add_option(
2984 '--no-color',
2985 action='store_true',
2986 help='Use this option to disable color output')
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002987 auth.add_auth_options(parser)
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002988 options, args = parser.parse_args(args)
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002989 auth_config = auth.extract_auth_config_from_options(options)
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002990
2991 author = RunGit(['config', 'user.email']).strip() or None
2992
vadimsh@chromium.orgcf6a5d22015-04-09 22:02:00 +00002993 cl = Changelist(auth_config=auth_config)
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002994
2995 if args:
2996 if len(args) > 1:
2997 parser.error('Unknown args')
2998 base_branch = args[0]
2999 else:
3000 # Default to diffing against the common ancestor of the upstream branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003001 base_branch = cl.GetCommonAncestorWithUpstream()
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00003002
3003 change = cl.GetChange(base_branch, None)
3004 return owners_finder.OwnersFinder(
3005 [f.LocalPath() for f in
3006 cl.GetChange(base_branch, None).AffectedFiles()],
3007 change.RepositoryRoot(), author,
3008 fopen=file, os_path=os.path, glob=glob.glob,
3009 disable_color=options.no_color).run()
3010
3011
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003012def BuildGitDiffCmd(diff_type, upstream_commit, args, extensions):
3013 """Generates a diff command."""
3014 # Generate diff for the current branch's changes.
3015 diff_cmd = ['diff', '--no-ext-diff', '--no-prefix', diff_type,
3016 upstream_commit, '--' ]
3017
3018 if args:
3019 for arg in args:
3020 if os.path.isdir(arg):
3021 diff_cmd.extend(os.path.join(arg, '*' + ext) for ext in extensions)
3022 elif os.path.isfile(arg):
3023 diff_cmd.append(arg)
3024 else:
3025 DieWithError('Argument "%s" is not a file or a directory' % arg)
3026 else:
3027 diff_cmd.extend('*' + ext for ext in extensions)
3028
3029 return diff_cmd
3030
3031
enne@chromium.org555cfe42014-01-29 18:21:39 +00003032@subcommand.usage('[files or directories to diff]')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003033def CMDformat(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003034 """Runs clang-format on the diff."""
thakis@chromium.org9819b1b2014-12-09 21:21:53 +00003035 CLANG_EXTS = ['.cc', '.cpp', '.h', '.mm', '.proto', '.java']
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003036 parser.add_option('--full', action='store_true',
3037 help='Reformat the full content of all touched files')
3038 parser.add_option('--dry-run', action='store_true',
3039 help='Don\'t modify any file on disk.')
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003040 parser.add_option('--diff', action='store_true',
3041 help='Print diff to stdout rather than modifying files.')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003042 opts, args = parser.parse_args(args)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003043
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00003044 # git diff generates paths against the root of the repository. Change
3045 # to that directory so clang-format can find files even within subdirs.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00003046 rel_base_path = settings.GetRelativeRoot()
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00003047 if rel_base_path:
3048 os.chdir(rel_base_path)
3049
digit@chromium.org29e47272013-05-17 17:01:46 +00003050 # Grab the merge-base commit, i.e. the upstream commit of the current
3051 # branch when it was created or the last time it was rebased. This is
3052 # to cover the case where the user may have called "git fetch origin",
3053 # moving the origin branch to a newer commit, but hasn't rebased yet.
3054 upstream_commit = None
3055 cl = Changelist()
3056 upstream_branch = cl.GetUpstreamBranch()
3057 if upstream_branch:
3058 upstream_commit = RunGit(['merge-base', 'HEAD', upstream_branch])
3059 upstream_commit = upstream_commit.strip()
3060
3061 if not upstream_commit:
3062 DieWithError('Could not find base commit for this branch. '
3063 'Are you in detached state?')
3064
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003065 if opts.full:
3066 # Only list the names of modified files.
3067 clang_diff_type = '--name-only'
enne@chromium.org555cfe42014-01-29 18:21:39 +00003068 else:
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003069 # Only generate context-less patches.
3070 clang_diff_type = '-U0'
3071
3072 diff_cmd = BuildGitDiffCmd(clang_diff_type, upstream_commit, args, CLANG_EXTS)
digit@chromium.org29e47272013-05-17 17:01:46 +00003073 diff_output = RunGit(diff_cmd)
3074
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003075 top_dir = os.path.normpath(
3076 RunGit(["rev-parse", "--show-toplevel"]).rstrip('\n'))
3077
3078 # Locate the clang-format binary in the checkout
3079 try:
3080 clang_format_tool = clang_format.FindClangFormatToolInChromiumTree()
3081 except clang_format.NotFoundError, e:
3082 DieWithError(e)
mdempsky@google.comc3b3dc02013-08-05 23:09:49 +00003083
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003084 # Set to 2 to signal to CheckPatchFormatted() that this patch isn't
3085 # formatted. This is used to block during the presubmit.
3086 return_value = 0
3087
digit@chromium.org29e47272013-05-17 17:01:46 +00003088 if opts.full:
3089 # diff_output is a list of files to send to clang-format.
3090 files = diff_output.splitlines()
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003091 if files:
3092 cmd = [clang_format_tool]
3093 if not opts.dry_run and not opts.diff:
3094 cmd.append('-i')
3095 stdout = RunCommand(cmd + files, cwd=top_dir)
3096 if opts.diff:
3097 sys.stdout.write(stdout)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003098 else:
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003099 env = os.environ.copy()
thakis@chromium.orgbd2a9b92014-09-05 18:54:22 +00003100 env['PATH'] = str(os.path.dirname(clang_format_tool))
digit@chromium.org29e47272013-05-17 17:01:46 +00003101 # diff_output is a patch to send to clang-format-diff.py
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00003102 try:
3103 script = clang_format.FindClangFormatScriptInChromiumTree(
3104 'clang-format-diff.py')
3105 except clang_format.NotFoundError, e:
3106 DieWithError(e)
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00003107
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003108 cmd = [sys.executable, script, '-p0']
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003109 if not opts.dry_run and not opts.diff:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003110 cmd.append('-i')
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00003111
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003112 stdout = RunCommand(cmd, stdin=diff_output, cwd=top_dir, env=env)
wittman@chromium.org04d5a222014-03-07 18:30:42 +00003113 if opts.diff:
3114 sys.stdout.write(stdout)
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00003115 if opts.dry_run and len(stdout) > 0:
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003116 return_value = 2
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003117
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +00003118 # Build a diff command that only operates on dart files. dart's formatter
3119 # does not have the nice property of only operating on modified chunks, so
3120 # hard code full.
3121 dart_diff_cmd = BuildGitDiffCmd('--name-only', upstream_commit,
3122 args, ['.dart'])
3123 dart_diff_output = RunGit(dart_diff_cmd)
3124 if dart_diff_output:
3125 try:
3126 command = [dart_format.FindDartFmtToolInChromiumTree()]
3127 if not opts.dry_run and not opts.diff:
3128 command.append('-w')
3129 command.extend(dart_diff_output.splitlines())
3130
3131 stdout = RunCommand(command, cwd=top_dir, env=env)
3132 if opts.dry_run and stdout:
3133 return_value = 2
3134 except dart_format.NotFoundError as e:
3135 print ('Unable to check dart code formatting. Dart SDK is not in ' +
3136 'this checkout.')
3137
3138 return return_value
agable@chromium.orgfab8f822013-05-06 17:43:09 +00003139
3140
maruel@chromium.org29404b52014-09-08 22:58:00 +00003141def CMDlol(parser, args):
3142 # This command is intentionally undocumented.
thakis@chromium.org3421c992014-11-02 02:20:32 +00003143 print zlib.decompress(base64.b64decode(
3144 'eNptkLEOwyAMRHe+wupCIqW57v0Vq84WqWtXyrcXnCBsmgMJ+/SSAxMZgRB6NzE'
3145 'E2ObgCKJooYdu4uAQVffUEoE1sRQLxAcqzd7uK2gmStrll1ucV3uZyaY5sXyDd9'
3146 'JAnN+lAXsOMJ90GANAi43mq5/VeeacylKVgi8o6F1SC63FxnagHfJUTfUYdCR/W'
3147 'Ofe+0dHL7PicpytKP750Fh1q2qnLVof4w8OZWNY'))
maruel@chromium.org29404b52014-09-08 22:58:00 +00003148 return 0
3149
3150
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003151class OptionParser(optparse.OptionParser):
3152 """Creates the option parse and add --verbose support."""
3153 def __init__(self, *args, **kwargs):
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003154 optparse.OptionParser.__init__(
3155 self, *args, prog='git cl', version=__version__, **kwargs)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003156 self.add_option(
3157 '-v', '--verbose', action='count', default=0,
3158 help='Use 2 times for more debugging info')
3159
3160 def parse_args(self, args=None, values=None):
3161 options, args = optparse.OptionParser.parse_args(self, args, values)
3162 levels = [logging.WARNING, logging.INFO, logging.DEBUG]
3163 logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)])
3164 return options, args
3165
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003166
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003167def main(argv):
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003168 if sys.hexversion < 0x02060000:
3169 print >> sys.stderr, (
3170 '\nYour python version %s is unsupported, please upgrade.\n' %
3171 sys.version.split(' ', 1)[0])
3172 return 2
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003173
maruel@chromium.orgddd59412011-11-30 14:20:38 +00003174 # Reload settings.
3175 global settings
3176 settings = Settings()
3177
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003178 colorize_CMDstatus_doc()
maruel@chromium.org0633fb42013-08-16 20:06:14 +00003179 dispatcher = subcommand.CommandDispatcher(__name__)
3180 try:
3181 return dispatcher.execute(OptionParser(), argv)
3182 except urllib2.HTTPError, e:
3183 if e.code != 500:
3184 raise
3185 DieWithError(
3186 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
3187 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
sbc@chromium.org013731e2015-02-26 18:28:43 +00003188 return 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00003189
3190
3191if __name__ == '__main__':
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003192 # These affect sys.stdout so do it outside of main() to simplify mocks in
3193 # unit testing.
maruel@chromium.org6f09cd92011-04-01 16:38:12 +00003194 fix_encoding.fix_encoding()
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00003195 colorama.init()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003196 try:
3197 sys.exit(main(sys.argv[1:]))
3198 except KeyboardInterrupt:
3199 sys.stderr.write('interrupted\n')
3200 sys.exit(1)