blob: b8263b5503c00a9f5371c133f8f13eea2dc28339 [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
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +000011import glob
maruel@chromium.org4f6852c2012-04-20 20:39:20 +000012import json
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000013import logging
14import optparse
15import os
maruel@chromium.org1033efd2013-07-23 23:25:09 +000016import Queue
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000017import re
ukai@chromium.org78c4b982012-02-14 02:20:26 +000018import stat
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000019import sys
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000020import textwrap
maruel@chromium.org1033efd2013-07-23 23:25:09 +000021import threading
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000022import urllib2
maruel@chromium.org967c0a82013-06-17 22:52:24 +000023import urlparse
thestig@chromium.org00858c82013-12-02 23:08:03 +000024import webbrowser
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000025
26try:
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000027 import readline # pylint: disable=F0401,W0611
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000028except ImportError:
29 pass
30
maruel@chromium.org2a74d372011-03-29 19:05:50 +000031
maruel@chromium.org2e23ce32013-05-07 12:42:28 +000032from third_party import colorama
maruel@chromium.org2a74d372011-03-29 19:05:50 +000033from third_party import upload
34import breakpad # pylint: disable=W0611
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +000035import clang_format
maruel@chromium.org6f09cd92011-04-01 16:38:12 +000036import fix_encoding
maruel@chromium.org0e0436a2011-10-25 13:32:41 +000037import gclient_utils
iannucci@chromium.org9e849272014-04-04 00:31:55 +000038import git_common
piman@chromium.org336f9122014-09-04 02:16:55 +000039import owners
iannucci@chromium.org9e849272014-04-04 00:31:55 +000040import owners_finder
maruel@chromium.org2a74d372011-03-29 19:05:50 +000041import presubmit_support
maruel@chromium.orgcab38e92011-04-09 00:30:51 +000042import rietveld
maruel@chromium.org2a74d372011-03-29 19:05:50 +000043import scm
maruel@chromium.org0633fb42013-08-16 20:06:14 +000044import subcommand
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000045import subprocess2
maruel@chromium.org2a74d372011-03-29 19:05:50 +000046import watchlists
47
maruel@chromium.org0633fb42013-08-16 20:06:14 +000048__version__ = '1.0'
maruel@chromium.org2a74d372011-03-29 19:05:50 +000049
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +000050DEFAULT_SERVER = 'https://codereview.appspot.com'
maruel@chromium.org0ba7f962011-01-11 22:13:58 +000051POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000052DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +000053GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit'
sivachandra@chromium.orgaebe87f2012-10-22 20:34:21 +000054CHANGE_ID = 'Change-Id:'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000055
thestig@chromium.org44202a22014-03-11 19:22:18 +000056# Valid extensions for files we want to lint.
57DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
58DEFAULT_LINT_IGNORE_REGEX = r"$^"
59
maruel@chromium.org2e23ce32013-05-07 12:42:28 +000060# Shortcut since it quickly becomes redundant.
61Fore = colorama.Fore
maruel@chromium.org90541732011-04-01 17:54:18 +000062
maruel@chromium.orgddd59412011-11-30 14:20:38 +000063# Initialized in main()
64settings = None
65
66
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000067def DieWithError(message):
dpranke@chromium.org970c5222011-03-12 00:32:24 +000068 print >> sys.stderr, message
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000069 sys.exit(1)
70
71
thestig@chromium.org8b0553c2014-02-11 00:33:37 +000072def GetNoGitPagerEnv():
73 env = os.environ.copy()
74 # 'cat' is a magical git string that disables pagers on all platforms.
75 env['GIT_PAGER'] = 'cat'
76 return env
77
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +000078
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000079def RunCommand(args, error_ok=False, error_message=None, **kwargs):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000080 try:
maruel@chromium.org373af802012-05-25 21:07:33 +000081 return subprocess2.check_output(args, shell=False, **kwargs)
maruel@chromium.org78936cb2013-04-11 00:17:52 +000082 except subprocess2.CalledProcessError as e:
83 logging.debug('Failed running %s', args)
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000084 if not error_ok:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000085 DieWithError(
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000086 'Command "%s" failed.\n%s' % (
87 ' '.join(args), error_message or e.stdout or ''))
88 return e.stdout
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000089
90
91def RunGit(args, **kwargs):
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000092 """Returns stdout."""
bratell@opera.com82b91cd2013-07-09 06:33:41 +000093 return RunCommand(['git'] + args, **kwargs)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +000094
95
enne@chromium.org3b7e15c2014-01-21 17:44:47 +000096def RunGitWithCode(args, suppress_stderr=False):
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +000097 """Returns return code and stdout."""
szager@chromium.org9bb85e22012-06-13 20:28:23 +000098 try:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +000099 if suppress_stderr:
100 stderr = subprocess2.VOID
101 else:
102 stderr = sys.stderr
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000103 out, code = subprocess2.communicate(['git'] + args,
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000104 env=GetNoGitPagerEnv(),
enne@chromium.org3b7e15c2014-01-21 17:44:47 +0000105 stdout=subprocess2.PIPE,
106 stderr=stderr)
szager@chromium.org9bb85e22012-06-13 20:28:23 +0000107 return code, out[0]
108 except ValueError:
109 # When the subprocess fails, it returns None. That triggers a ValueError
110 # when trying to unpack the return value into (out, code).
111 return 1, ''
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000112
113
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000114def IsGitVersionAtLeast(min_version):
ilevy@chromium.orgcc56ee42013-07-10 22:16:29 +0000115 prefix = 'git version '
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000116 version = RunGit(['--version']).strip()
ilevy@chromium.orgcc56ee42013-07-10 22:16:29 +0000117 return (version.startswith(prefix) and
118 LooseVersion(version[len(prefix):]) >= LooseVersion(min_version))
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +0000119
120
maruel@chromium.org90541732011-04-01 17:54:18 +0000121def ask_for_data(prompt):
122 try:
123 return raw_input(prompt)
124 except KeyboardInterrupt:
125 # Hide the exception.
126 sys.exit(1)
127
128
iannucci@chromium.org79540052012-10-19 23:15:26 +0000129def git_set_branch_value(key, value):
130 branch = Changelist().GetBranch()
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +0000131 if not branch:
132 return
133
134 cmd = ['config']
135 if isinstance(value, int):
136 cmd.append('--int')
137 git_key = 'branch.%s.%s' % (branch, key)
138 RunGit(cmd + [git_key, str(value)])
iannucci@chromium.org79540052012-10-19 23:15:26 +0000139
140
141def git_get_branch_default(key, default):
142 branch = Changelist().GetBranch()
143 if branch:
144 git_key = 'branch.%s.%s' % (branch, key)
145 (_, stdout) = RunGitWithCode(['config', '--int', '--get', git_key])
146 try:
147 return int(stdout.strip())
148 except ValueError:
149 pass
150 return default
151
152
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000153def add_git_similarity(parser):
154 parser.add_option(
iannucci@chromium.org79540052012-10-19 23:15:26 +0000155 '--similarity', metavar='SIM', type='int', action='store',
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000156 help='Sets the percentage that a pair of files need to match in order to'
157 ' be considered copies (default 50)')
iannucci@chromium.org79540052012-10-19 23:15:26 +0000158 parser.add_option(
159 '--find-copies', action='store_true',
160 help='Allows git to look for copies.')
161 parser.add_option(
162 '--no-find-copies', action='store_false', dest='find_copies',
163 help='Disallows git from looking for copies.')
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000164
165 old_parser_args = parser.parse_args
166 def Parse(args):
167 options, args = old_parser_args(args)
168
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000169 if options.similarity is None:
iannucci@chromium.org79540052012-10-19 23:15:26 +0000170 options.similarity = git_get_branch_default('git-cl-similarity', 50)
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000171 else:
iannucci@chromium.org79540052012-10-19 23:15:26 +0000172 print('Note: Saving similarity of %d%% in git config.'
173 % options.similarity)
174 git_set_branch_value('git-cl-similarity', options.similarity)
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000175
iannucci@chromium.org79540052012-10-19 23:15:26 +0000176 options.similarity = max(0, min(options.similarity, 100))
177
178 if options.find_copies is None:
179 options.find_copies = bool(
180 git_get_branch_default('git-find-copies', True))
181 else:
182 git_set_branch_value('git-find-copies', int(options.find_copies))
iannucci@chromium.org53937ba2012-10-02 18:20:43 +0000183
184 print('Using %d%% similarity for rename/copy detection. '
185 'Override with --similarity.' % options.similarity)
186
187 return options, args
188 parser.parse_args = Parse
189
190
ukai@chromium.org259e4682012-10-25 07:36:33 +0000191def is_dirty_git_tree(cmd):
192 # Make sure index is up-to-date before running diff-index.
193 RunGit(['update-index', '--refresh', '-q'], error_ok=True)
194 dirty = RunGit(['diff-index', '--name-status', 'HEAD'])
195 if dirty:
196 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd
197 print 'Uncommitted files: (git diff-index --name-status HEAD)'
198 print dirty[:4096]
199 if len(dirty) > 4096:
200 print '... (run "git diff-index --name-status HEAD" to see full output).'
201 return True
202 return False
203
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000204
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
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000285 self.pending_ref_prefix = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000286
287 def LazyUpdateIfNeeded(self):
288 """Updates the settings from a codereview.settings file, if available."""
289 if not self.updated:
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000290 # The only value that actually changes the behavior is
291 # autoupdate = "false". Everything else means "true".
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000292 autoupdate = RunGit(['config', 'rietveld.autoupdate'],
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000293 error_ok=True
294 ).strip().lower()
295
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000296 cr_settings_file = FindCodereviewSettingsFile()
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000297 if autoupdate != 'false' and cr_settings_file:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000298 LoadCodereviewSettingsFromFile(cr_settings_file)
pgervais@chromium.org87884cc2014-01-03 22:23:41 +0000299 # set updated to True to avoid infinite calling loop
300 # through DownloadHooks
ukai@chromium.org78c4b982012-02-14 02:20:26 +0000301 self.updated = True
302 DownloadHooks(False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000303 self.updated = True
304
305 def GetDefaultServerUrl(self, error_ok=False):
306 if not self.default_server:
307 self.LazyUpdateIfNeeded()
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000308 self.default_server = gclient_utils.UpgradeToHttps(
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000309 self._GetRietveldConfig('server', error_ok=True))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000310 if error_ok:
311 return self.default_server
312 if not self.default_server:
313 error_message = ('Could not find settings file. You must configure '
314 'your review setup by running "git cl config".')
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000315 self.default_server = gclient_utils.UpgradeToHttps(
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000316 self._GetRietveldConfig('server', error_message=error_message))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000317 return self.default_server
318
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000319 @staticmethod
320 def GetRelativeRoot():
321 return RunGit(['rev-parse', '--show-cdup']).strip()
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000322
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000323 def GetRoot(self):
thestig@chromium.org7a54e812014-02-11 19:57:22 +0000324 if self.root is None:
325 self.root = os.path.abspath(self.GetRelativeRoot())
326 return self.root
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000327
328 def GetIsGitSvn(self):
329 """Return true if this repo looks like it's using git-svn."""
330 if self.is_git_svn is None:
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000331 if self.GetPendingRefPrefix():
332 # If PENDING_REF_PREFIX is set then it's a pure git repo no matter what.
333 self.is_git_svn = False
334 else:
335 # If you have any "svn-remote.*" config keys, we think you're using svn.
336 self.is_git_svn = RunGitWithCode(
337 ['config', '--local', '--get-regexp', r'^svn-remote\.'])[0] == 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000338 return self.is_git_svn
339
340 def GetSVNBranch(self):
341 if self.svn_branch is None:
342 if not self.GetIsGitSvn():
343 DieWithError('Repo doesn\'t appear to be a git-svn repo.')
344
345 # Try to figure out which remote branch we're based on.
346 # Strategy:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000347 # 1) iterate through our branch history and find the svn URL.
348 # 2) find the svn-remote that fetches from the URL.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000349
350 # regexp matching the git-svn line that contains the URL.
351 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
352
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000353 # We don't want to go through all of history, so read a line from the
354 # pipe at a time.
355 # The -100 is an arbitrary limit so we don't search forever.
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000356 cmd = ['git', 'log', '-100', '--pretty=medium']
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000357 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE,
358 env=GetNoGitPagerEnv())
maruel@chromium.org740f9d72011-06-10 18:33:10 +0000359 url = None
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000360 for line in proc.stdout:
361 match = git_svn_re.match(line)
362 if match:
363 url = match.group(1)
364 proc.stdout.close() # Cut pipe.
365 break
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000366
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000367 if url:
368 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$')
369 remotes = RunGit(['config', '--get-regexp',
370 r'^svn-remote\..*\.url']).splitlines()
371 for remote in remotes:
372 match = svn_remote_re.match(remote)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000373 if match:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000374 remote = match.group(1)
375 base_url = match.group(2)
szager@chromium.org4ac25532013-12-16 22:07:02 +0000376 rewrite_root = RunGit(
377 ['config', 'svn-remote.%s.rewriteRoot' % remote],
378 error_ok=True).strip()
379 if rewrite_root:
380 base_url = rewrite_root
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000381 fetch_spec = RunGit(
bauerb@chromium.org866276c2011-03-18 20:09:31 +0000382 ['config', 'svn-remote.%s.fetch' % remote],
383 error_ok=True).strip()
384 if fetch_spec:
385 self.svn_branch = MatchSvnGlob(url, base_url, fetch_spec, False)
386 if self.svn_branch:
387 break
388 branch_spec = RunGit(
389 ['config', 'svn-remote.%s.branches' % remote],
390 error_ok=True).strip()
391 if branch_spec:
392 self.svn_branch = MatchSvnGlob(url, base_url, branch_spec, True)
393 if self.svn_branch:
394 break
395 tag_spec = RunGit(
396 ['config', 'svn-remote.%s.tags' % remote],
397 error_ok=True).strip()
398 if tag_spec:
399 self.svn_branch = MatchSvnGlob(url, base_url, tag_spec, True)
400 if self.svn_branch:
401 break
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000402
403 if not self.svn_branch:
404 DieWithError('Can\'t guess svn branch -- try specifying it on the '
405 'command line')
406
407 return self.svn_branch
408
409 def GetTreeStatusUrl(self, error_ok=False):
410 if not self.tree_status_url:
411 error_message = ('You must configure your tree status URL by running '
412 '"git cl config".')
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000413 self.tree_status_url = self._GetRietveldConfig(
414 'tree-status-url', error_ok=error_ok, error_message=error_message)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000415 return self.tree_status_url
416
417 def GetViewVCUrl(self):
418 if not self.viewvc_url:
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000419 self.viewvc_url = self._GetRietveldConfig('viewvc-url', error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000420 return self.viewvc_url
421
rmistry@google.com90752582014-01-14 21:04:50 +0000422 def GetBugPrefix(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000423 return self._GetRietveldConfig('bug-prefix', error_ok=True)
rmistry@google.com90752582014-01-14 21:04:50 +0000424
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000425 def GetDefaultCCList(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000426 return self._GetRietveldConfig('cc', error_ok=True)
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000427
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +0000428 def GetDefaultPrivateFlag(self):
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000429 return self._GetRietveldConfig('private', error_ok=True)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +0000430
ukai@chromium.orge8077812012-02-03 03:41:46 +0000431 def GetIsGerrit(self):
432 """Return true if this repo is assosiated with gerrit code review system."""
433 if self.is_gerrit is None:
434 self.is_gerrit = self._GetConfig('gerrit.host', error_ok=True)
435 return self.is_gerrit
436
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000437 def GetGitEditor(self):
438 """Return the editor specified in the git config, or None if none is."""
439 if self.git_editor is None:
440 self.git_editor = self._GetConfig('core.editor', error_ok=True)
441 return self.git_editor or None
442
thestig@chromium.org44202a22014-03-11 19:22:18 +0000443 def GetLintRegex(self):
444 return (self._GetRietveldConfig('cpplint-regex', error_ok=True) or
445 DEFAULT_LINT_REGEX)
446
447 def GetLintIgnoreRegex(self):
448 return (self._GetRietveldConfig('cpplint-ignore-regex', error_ok=True) or
449 DEFAULT_LINT_IGNORE_REGEX)
450
sheyang@chromium.org152cf832014-06-11 21:37:49 +0000451 def GetProject(self):
452 if not self.project:
453 self.project = self._GetRietveldConfig('project', error_ok=True)
454 return self.project
455
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +0000456 def GetPendingRefPrefix(self):
457 if not self.pending_ref_prefix:
458 self.pending_ref_prefix = self._GetRietveldConfig(
459 'pending-ref-prefix', error_ok=True)
460 return self.pending_ref_prefix
461
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000462 def _GetRietveldConfig(self, param, **kwargs):
463 return self._GetConfig('rietveld.' + param, **kwargs)
464
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000465 def _GetConfig(self, param, **kwargs):
466 self.LazyUpdateIfNeeded()
467 return RunGit(['config', param], **kwargs).strip()
468
469
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000470def ShortBranchName(branch):
471 """Convert a name like 'refs/heads/foo' to just 'foo'."""
472 return branch.replace('refs/heads/', '')
473
474
475class Changelist(object):
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000476 def __init__(self, branchref=None, issue=None):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000477 # Poke settings so we get the "configure your server" message if necessary.
maruel@chromium.org379d07a2011-11-30 14:58:10 +0000478 global settings
479 if not settings:
480 # Happens when git_cl.py is used as a utility library.
481 settings = Settings()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000482 settings.GetDefaultServerUrl()
483 self.branchref = branchref
484 if self.branchref:
485 self.branch = ShortBranchName(self.branchref)
486 else:
487 self.branch = None
488 self.rietveld_server = None
489 self.upstream_branch = None
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000490 self.lookedup_issue = False
491 self.issue = issue or None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000492 self.has_description = False
493 self.description = None
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000494 self.lookedup_patchset = False
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000495 self.patchset = None
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000496 self._rpc_server = None
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000497 self.cc = None
498 self.watchers = ()
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000499 self._remote = None
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000500 self._props = None
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000501
502 def GetCCList(self):
503 """Return the users cc'd on this CL.
504
505 Return is a string suitable for passing to gcl with the --cc flag.
506 """
507 if self.cc is None:
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +0000508 base_cc = settings.GetDefaultCCList()
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000509 more_cc = ','.join(self.watchers)
510 self.cc = ','.join(filter(None, (base_cc, more_cc))) or ''
511 return self.cc
512
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +0000513 def GetCCListWithoutDefault(self):
514 """Return the users cc'd on this CL excluding default ones."""
515 if self.cc is None:
516 self.cc = ','.join(self.watchers)
517 return self.cc
518
bauerb@chromium.orgae6df352011-04-06 17:40:39 +0000519 def SetWatchers(self, watchers):
520 """Set the list of email addresses that should be cc'd based on the changed
521 files in this CL.
522 """
523 self.watchers = watchers
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000524
525 def GetBranch(self):
526 """Returns the short branch name, e.g. 'master'."""
527 if not self.branch:
528 self.branchref = RunGit(['symbolic-ref', 'HEAD']).strip()
529 self.branch = ShortBranchName(self.branchref)
530 return self.branch
531
532 def GetBranchRef(self):
533 """Returns the full branch name, e.g. 'refs/heads/master'."""
534 self.GetBranch() # Poke the lazy loader.
535 return self.branchref
536
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000537 @staticmethod
538 def FetchUpstreamTuple(branch):
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +0000539 """Returns a tuple containing remote and remote ref,
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000540 e.g. 'origin', 'refs/heads/master'
541 """
542 remote = '.'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000543 upstream_branch = RunGit(['config', 'branch.%s.merge' % branch],
544 error_ok=True).strip()
545 if upstream_branch:
546 remote = RunGit(['config', 'branch.%s.remote' % branch]).strip()
547 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000548 upstream_branch = RunGit(['config', 'rietveld.upstream-branch'],
549 error_ok=True).strip()
550 if upstream_branch:
551 remote = RunGit(['config', 'rietveld.upstream-remote']).strip()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000552 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000553 # Fall back on trying a git-svn upstream branch.
554 if settings.GetIsGitSvn():
555 upstream_branch = settings.GetSVNBranch()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000556 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000557 # Else, try to guess the origin remote.
558 remote_branches = RunGit(['branch', '-r']).split()
559 if 'origin/master' in remote_branches:
560 # Fall back on origin/master if it exits.
561 remote = 'origin'
562 upstream_branch = 'refs/heads/master'
563 elif 'origin/trunk' in remote_branches:
564 # Fall back on origin/trunk if it exists. Generally a shared
565 # git-svn clone
566 remote = 'origin'
567 upstream_branch = 'refs/heads/trunk'
568 else:
569 DieWithError("""Unable to determine default branch to diff against.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000570Either pass complete "git diff"-style arguments, like
571 git cl upload origin/master
572or verify this branch is set up to track another (via the --track argument to
573"git checkout -b ...").""")
574
575 return remote, upstream_branch
576
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000577 def GetCommonAncestorWithUpstream(self):
iannucci@chromium.org9e849272014-04-04 00:31:55 +0000578 return git_common.get_or_create_merge_base(self.GetBranch(),
579 self.GetUpstreamBranch())
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000580
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000581 def GetUpstreamBranch(self):
582 if self.upstream_branch is None:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000583 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000584 if remote is not '.':
mmoss@chromium.orge7585452014-08-24 01:41:11 +0000585 upstream_branch = upstream_branch.replace('refs/heads/',
586 'refs/remotes/%s/' % remote)
587 upstream_branch = upstream_branch.replace('refs/branch-heads/',
588 'refs/remotes/branch-heads/')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000589 self.upstream_branch = upstream_branch
590 return self.upstream_branch
591
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000592 def GetRemoteBranch(self):
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000593 if not self._remote:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000594 remote, branch = None, self.GetBranch()
595 seen_branches = set()
596 while branch not in seen_branches:
597 seen_branches.add(branch)
598 remote, branch = self.FetchUpstreamTuple(branch)
599 branch = ShortBranchName(branch)
600 if remote != '.' or branch.startswith('refs/remotes'):
601 break
602 else:
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000603 remotes = RunGit(['remote'], error_ok=True).split()
604 if len(remotes) == 1:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000605 remote, = remotes
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000606 elif 'origin' in remotes:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000607 remote = 'origin'
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000608 logging.warning('Could not determine which remote this change is '
609 'associated with, so defaulting to "%s". This may '
610 'not be what you want. You may prevent this message '
611 'by running "git svn info" as documented here: %s',
612 self._remote,
613 GIT_INSTRUCTIONS_URL)
614 else:
615 logging.warn('Could not determine which remote this change is '
616 'associated with. You may prevent this message by '
617 'running "git svn info" as documented here: %s',
618 GIT_INSTRUCTIONS_URL)
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000619 branch = 'HEAD'
620 if branch.startswith('refs/remotes'):
621 self._remote = (remote, branch)
mmoss@chromium.orge7585452014-08-24 01:41:11 +0000622 elif branch.startswith('refs/branch-heads/'):
623 self._remote = (remote, branch.replace('refs/', 'refs/remotes/'))
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000624 else:
625 self._remote = (remote, 'refs/remotes/%s/%s' % (remote, branch))
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000626 return self._remote
627
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000628 def GitSanityChecks(self, upstream_git_obj):
629 """Checks git repo status and ensures diff is from local commits."""
630
631 # Verify the commit we're diffing against is in our current branch.
632 upstream_sha = RunGit(['rev-parse', '--verify', upstream_git_obj]).strip()
633 common_ancestor = RunGit(['merge-base', upstream_sha, 'HEAD']).strip()
634 if upstream_sha != common_ancestor:
635 print >> sys.stderr, (
636 'ERROR: %s is not in the current branch. You may need to rebase '
637 'your tracking branch' % upstream_sha)
638 return False
639
640 # List the commits inside the diff, and verify they are all local.
641 commits_in_diff = RunGit(
642 ['rev-list', '^%s' % upstream_sha, 'HEAD']).splitlines()
643 code, remote_branch = RunGitWithCode(['config', 'gitcl.remotebranch'])
644 remote_branch = remote_branch.strip()
645 if code != 0:
646 _, remote_branch = self.GetRemoteBranch()
647
648 commits_in_remote = RunGit(
649 ['rev-list', '^%s' % upstream_sha, remote_branch]).splitlines()
650
651 common_commits = set(commits_in_diff) & set(commits_in_remote)
652 if common_commits:
653 print >> sys.stderr, (
654 'ERROR: Your diff contains %d commits already in %s.\n'
655 'Run "git log --oneline %s..HEAD" to get a list of commits in '
656 'the diff. If you are using a custom git flow, you can override'
657 ' the reference used for this check with "git config '
658 'gitcl.remotebranch <git-ref>".' % (
659 len(common_commits), remote_branch, upstream_git_obj))
660 return False
661 return True
662
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +0000663 def GetGitBaseUrlFromConfig(self):
sheyang@chromium.orga656e702014-05-15 20:43:05 +0000664 """Return the configured base URL from branch.<branchname>.baseurl.
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +0000665
666 Returns None if it is not set.
667 """
sheyang@chromium.orga656e702014-05-15 20:43:05 +0000668 return RunGit(['config', 'branch.%s.base-url' % self.GetBranch()],
669 error_ok=True).strip()
jmbaker@chromium.orga2cbbbb2012-03-22 20:40:40 +0000670
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000671 def GetRemoteUrl(self):
672 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'.
673
674 Returns None if there is no remote.
675 """
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000676 remote, _ = self.GetRemoteBranch()
dyen@chromium.org2a13d4f2014-06-13 00:06:37 +0000677 url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip()
678
679 # If URL is pointing to a local directory, it is probably a git cache.
680 if os.path.isdir(url):
681 url = RunGit(['config', 'remote.%s.url' % remote],
682 error_ok=True,
683 cwd=url).strip()
684 return url
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000685
686 def GetIssue(self):
maruel@chromium.org52424302012-08-29 15:14:30 +0000687 """Returns the issue number as a int or None if not set."""
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000688 if self.issue is None and not self.lookedup_issue:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000689 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip()
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000690 self.issue = int(issue) or None if issue else None
691 self.lookedup_issue = True
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000692 return self.issue
693
694 def GetRietveldServer(self):
evan@chromium.org0af9b702012-02-11 00:42:16 +0000695 if not self.rietveld_server:
696 # If we're on a branch then get the server potentially associated
697 # with that branch.
698 if self.GetIssue():
699 self.rietveld_server = gclient_utils.UpgradeToHttps(RunGit(
700 ['config', self._RietveldServer()], error_ok=True).strip())
701 if not self.rietveld_server:
702 self.rietveld_server = settings.GetDefaultServerUrl()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000703 return self.rietveld_server
704
705 def GetIssueURL(self):
706 """Get the URL for a particular issue."""
dbeam@chromium.org015fd3d2013-06-18 19:02:50 +0000707 if not self.GetIssue():
708 return None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000709 return '%s/%s' % (self.GetRietveldServer(), self.GetIssue())
710
711 def GetDescription(self, pretty=False):
712 if not self.has_description:
713 if self.GetIssue():
maruel@chromium.org52424302012-08-29 15:14:30 +0000714 issue = self.GetIssue()
miket@chromium.org183df1a2012-01-04 19:44:55 +0000715 try:
716 self.description = self.RpcServer().get_description(issue).strip()
maruel@chromium.org85616e02014-07-28 15:37:55 +0000717 except urllib2.HTTPError as e:
miket@chromium.org183df1a2012-01-04 19:44:55 +0000718 if e.code == 404:
719 DieWithError(
720 ('\nWhile fetching the description for issue %d, received a '
721 '404 (not found)\n'
722 'error. It is likely that you deleted this '
723 'issue on the server. If this is the\n'
724 'case, please run\n\n'
725 ' git cl issue 0\n\n'
726 'to clear the association with the deleted issue. Then run '
727 'this command again.') % issue)
728 else:
729 DieWithError(
yujie.mao@intel.comdaee1d32013-12-18 11:55:03 +0000730 '\nFailed to fetch issue description. HTTP error %d' % e.code)
maruel@chromium.org85616e02014-07-28 15:37:55 +0000731 except urllib2.URLError as e:
732 print >> sys.stderr, (
733 'Warning: Failed to retrieve CL description due to network '
734 'failure.')
735 self.description = ''
736
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000737 self.has_description = True
738 if pretty:
739 wrapper = textwrap.TextWrapper()
740 wrapper.initial_indent = wrapper.subsequent_indent = ' '
741 return wrapper.fill(self.description)
742 return self.description
743
744 def GetPatchset(self):
maruel@chromium.org52424302012-08-29 15:14:30 +0000745 """Returns the patchset number as a int or None if not set."""
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000746 if self.patchset is None and not self.lookedup_patchset:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000747 patchset = RunGit(['config', self._PatchsetSetting()],
748 error_ok=True).strip()
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000749 self.patchset = int(patchset) or None if patchset else None
750 self.lookedup_patchset = True
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000751 return self.patchset
752
753 def SetPatchset(self, patchset):
754 """Set this branch's patchset. If patchset=0, clears the patchset."""
755 if patchset:
756 RunGit(['config', self._PatchsetSetting(), str(patchset)])
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000757 self.patchset = patchset
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000758 else:
759 RunGit(['config', '--unset', self._PatchsetSetting()],
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +0000760 stderr=subprocess2.PIPE, error_ok=True)
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000761 self.patchset = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000762
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000763 def GetMostRecentPatchset(self):
764 return self.GetIssueProperties()['patchsets'][-1]
binji@chromium.org0281f522012-09-14 13:37:59 +0000765
766 def GetPatchSetDiff(self, issue, patchset):
maruel@chromium.org27bb3872011-05-30 20:33:19 +0000767 return self.RpcServer().get(
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000768 '/download/issue%s_%s.diff' % (issue, patchset))
769
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000770 def GetIssueProperties(self):
771 if self._props is None:
772 issue = self.GetIssue()
773 if not issue:
774 self._props = {}
775 else:
776 self._props = self.RpcServer().get_issue_properties(issue, True)
777 return self._props
778
maruel@chromium.orgcf087782013-07-23 13:08:48 +0000779 def GetApprovingReviewers(self):
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000780 return get_approving_reviewers(self.GetIssueProperties())
maruel@chromium.orge52678e2013-04-26 18:34:44 +0000781
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000782 def SetIssue(self, issue):
783 """Set this branch's issue. If issue=0, clears the issue."""
784 if issue:
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000785 self.issue = issue
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000786 RunGit(['config', self._IssueSetting(), str(issue)])
787 if self.rietveld_server:
788 RunGit(['config', self._RietveldServer(), self.rietveld_server])
789 else:
teravest@chromium.orgd79d4b82013-10-23 20:09:08 +0000790 current_issue = self.GetIssue()
791 if current_issue:
792 RunGit(['config', '--unset', self._IssueSetting()])
maruel@chromium.org1033efd2013-07-23 23:25:09 +0000793 self.issue = None
794 self.SetPatchset(None)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000795
asvitkine@chromium.org15169952011-09-27 14:30:53 +0000796 def GetChange(self, upstream_branch, author):
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +0000797 if not self.GitSanityChecks(upstream_branch):
798 DieWithError('\nGit sanity check failure')
799
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000800 root = settings.GetRelativeRoot()
bratell@opera.comf267b0e2013-05-02 09:11:43 +0000801 if not root:
802 root = '.'
bauerb@chromium.org512f1ef2011-04-20 15:17:57 +0000803 absroot = os.path.abspath(root)
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000804
805 # We use the sha1 of HEAD as a name of this change.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000806 name = RunGitWithCode(['rev-parse', 'HEAD'])[1].strip()
bauerb@chromium.org512f1ef2011-04-20 15:17:57 +0000807 # Need to pass a relative path for msysgit.
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +0000808 try:
maruel@chromium.org80a9ef12011-12-13 20:44:10 +0000809 files = scm.GIT.CaptureStatus([root], '.', upstream_branch)
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +0000810 except subprocess2.CalledProcessError:
811 DieWithError(
pgervais@chromium.orgd6617f32013-11-19 00:34:54 +0000812 ('\nFailed to diff against upstream branch %s\n\n'
maruel@chromium.org2b38e9c2011-10-19 00:04:35 +0000813 'This branch probably doesn\'t exist anymore. To reset the\n'
814 'tracking branch, please run\n'
815 ' git branch --set-upstream %s trunk\n'
816 'replacing trunk with origin/master or the relevant branch') %
817 (upstream_branch, self.GetBranch()))
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000818
maruel@chromium.org52424302012-08-29 15:14:30 +0000819 issue = self.GetIssue()
820 patchset = self.GetPatchset()
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000821 if issue:
822 description = self.GetDescription()
823 else:
824 # If the change was never uploaded, use the log messages of all commits
825 # up to the branch point, as git cl upload will prefill the description
826 # with these log messages.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +0000827 args = ['log', '--pretty=format:%s%n%n%b', '%s...' % (upstream_branch)]
828 description = RunGitWithCode(args)[1].strip()
maruel@chromium.org03b3bdc2011-06-14 13:04:12 +0000829
830 if not author:
maruel@chromium.org13f623c2011-07-22 16:02:23 +0000831 author = RunGit(['config', 'user.email']).strip() or None
asvitkine@chromium.org15169952011-09-27 14:30:53 +0000832 return presubmit_support.GitChange(
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000833 name,
834 description,
835 absroot,
836 files,
837 issue,
838 patchset,
agable@chromium.orgea84ef12014-04-30 19:55:12 +0000839 author,
840 upstream=upstream_branch)
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000841
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +0000842 def RunHook(self, committing, may_prompt, verbose, change):
asvitkine@chromium.org15169952011-09-27 14:30:53 +0000843 """Calls sys.exit() if the hook fails; returns a HookResults otherwise."""
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000844
845 try:
maruel@chromium.orgb0a63912012-01-17 18:10:16 +0000846 return presubmit_support.DoPresubmitChecks(change, committing,
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000847 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin,
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +0000848 default_presubmit=None, may_prompt=may_prompt,
maruel@chromium.org239f4112011-06-03 20:08:23 +0000849 rietveld_obj=self.RpcServer())
bauerb@chromium.org6fb99c62011-04-18 15:57:28 +0000850 except presubmit_support.PresubmitFailure, e:
851 DieWithError(
852 ('%s\nMaybe your depot_tools is out of date?\n'
853 'If all fails, contact maruel@') % e)
854
maruel@chromium.orgb021b322013-04-08 17:57:29 +0000855 def UpdateDescription(self, description):
856 self.description = description
857 return self.RpcServer().update_description(
858 self.GetIssue(), self.description)
859
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000860 def CloseIssue(self):
maruel@chromium.org607bb1b2011-06-01 23:43:11 +0000861 """Updates the description and closes the issue."""
maruel@chromium.orgb021b322013-04-08 17:57:29 +0000862 return self.RpcServer().close_issue(self.GetIssue())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000863
maruel@chromium.org27bb3872011-05-30 20:33:19 +0000864 def SetFlag(self, flag, value):
865 """Patchset must match."""
866 if not self.GetPatchset():
867 DieWithError('The patchset needs to match. Send another patchset.')
868 try:
869 return self.RpcServer().set_flag(
maruel@chromium.org52424302012-08-29 15:14:30 +0000870 self.GetIssue(), self.GetPatchset(), flag, value)
maruel@chromium.org27bb3872011-05-30 20:33:19 +0000871 except urllib2.HTTPError, e:
872 if e.code == 404:
873 DieWithError('The issue %s doesn\'t exist.' % self.GetIssue())
874 if e.code == 403:
875 DieWithError(
876 ('Access denied to issue %s. Maybe the patchset %s doesn\'t '
877 'match?') % (self.GetIssue(), self.GetPatchset()))
878 raise
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000879
maruel@chromium.orgcab38e92011-04-09 00:30:51 +0000880 def RpcServer(self):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000881 """Returns an upload.RpcServer() to access this review's rietveld instance.
882 """
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000883 if not self._rpc_server:
maruel@chromium.org4bac4b52012-11-27 20:33:52 +0000884 self._rpc_server = rietveld.CachingRietveld(
885 self.GetRietveldServer(), None, None)
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +0000886 return self._rpc_server
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000887
888 def _IssueSetting(self):
889 """Return the git setting that stores this change's issue."""
890 return 'branch.%s.rietveldissue' % self.GetBranch()
891
892 def _PatchsetSetting(self):
893 """Return the git setting that stores this change's most recent patchset."""
894 return 'branch.%s.rietveldpatchset' % self.GetBranch()
895
896 def _RietveldServer(self):
897 """Returns the git setting that stores this change's rietveld server."""
898 return 'branch.%s.rietveldserver' % self.GetBranch()
899
900
901def GetCodereviewSettingsInteractively():
902 """Prompt the user for settings."""
ukai@chromium.orge8077812012-02-03 03:41:46 +0000903 # TODO(ukai): ask code review system is rietveld or gerrit?
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000904 server = settings.GetDefaultServerUrl(error_ok=True)
905 prompt = 'Rietveld server (host[:port])'
906 prompt += ' [%s]' % (server or DEFAULT_SERVER)
maruel@chromium.org90541732011-04-01 17:54:18 +0000907 newserver = ask_for_data(prompt + ':')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000908 if not server and not newserver:
909 newserver = DEFAULT_SERVER
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000910 if newserver:
911 newserver = gclient_utils.UpgradeToHttps(newserver)
912 if newserver != server:
913 RunGit(['config', 'rietveld.server', newserver])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000914
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000915 def SetProperty(initial, caption, name, is_url):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000916 prompt = caption
917 if initial:
918 prompt += ' ("x" to clear) [%s]' % initial
maruel@chromium.org90541732011-04-01 17:54:18 +0000919 new_val = ask_for_data(prompt + ':')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000920 if new_val == 'x':
921 RunGit(['config', '--unset-all', 'rietveld.' + name], error_ok=True)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000922 elif new_val:
923 if is_url:
924 new_val = gclient_utils.UpgradeToHttps(new_val)
925 if new_val != initial:
926 RunGit(['config', 'rietveld.' + name, new_val])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000927
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000928 SetProperty(settings.GetDefaultCCList(), 'CC list', 'cc', False)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +0000929 SetProperty(settings.GetDefaultPrivateFlag(),
930 'Private flag (rietveld only)', 'private', False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000931 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL',
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000932 'tree-status-url', False)
933 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url', True)
rmistry@google.com90752582014-01-14 21:04:50 +0000934 SetProperty(settings.GetBugPrefix(), 'Bug Prefix', 'bug-prefix', False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +0000935
936 # TODO: configure a default branch to diff against, rather than this
937 # svn-based hackery.
938
939
dpranke@chromium.org20254fc2011-03-22 18:28:59 +0000940class ChangeDescription(object):
941 """Contains a parsed form of the change description."""
maruel@chromium.orgc6f60e82013-04-19 17:01:57 +0000942 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$'
agable@chromium.org42c20792013-09-12 17:34:49 +0000943 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$'
dpranke@chromium.org20254fc2011-03-22 18:28:59 +0000944
maruel@chromium.org78936cb2013-04-11 00:17:52 +0000945 def __init__(self, description):
agable@chromium.org42c20792013-09-12 17:34:49 +0000946 self._description_lines = (description or '').strip().splitlines()
maruel@chromium.org78936cb2013-04-11 00:17:52 +0000947
agable@chromium.org42c20792013-09-12 17:34:49 +0000948 @property # www.logilab.org/ticket/89786
949 def description(self): # pylint: disable=E0202
950 return '\n'.join(self._description_lines)
951
952 def set_description(self, desc):
953 if isinstance(desc, basestring):
954 lines = desc.splitlines()
955 else:
956 lines = [line.rstrip() for line in desc]
957 while lines and not lines[0]:
958 lines.pop(0)
959 while lines and not lines[-1]:
960 lines.pop(-1)
961 self._description_lines = lines
maruel@chromium.org78936cb2013-04-11 00:17:52 +0000962
piman@chromium.org336f9122014-09-04 02:16:55 +0000963 def update_reviewers(self, reviewers, add_owners_tbr=False, change=None):
agable@chromium.org42c20792013-09-12 17:34:49 +0000964 """Rewrites the R=/TBR= line(s) as a single line each."""
maruel@chromium.org78936cb2013-04-11 00:17:52 +0000965 assert isinstance(reviewers, list), reviewers
piman@chromium.org336f9122014-09-04 02:16:55 +0000966 if not reviewers and not add_owners_tbr:
maruel@chromium.org78936cb2013-04-11 00:17:52 +0000967 return
agable@chromium.org42c20792013-09-12 17:34:49 +0000968 reviewers = reviewers[:]
maruel@chromium.org78936cb2013-04-11 00:17:52 +0000969
agable@chromium.org42c20792013-09-12 17:34:49 +0000970 # Get the set of R= and TBR= lines and remove them from the desciption.
971 regexp = re.compile(self.R_LINE)
972 matches = [regexp.match(line) for line in self._description_lines]
973 new_desc = [l for i, l in enumerate(self._description_lines)
974 if not matches[i]]
975 self.set_description(new_desc)
maruel@chromium.org78936cb2013-04-11 00:17:52 +0000976
agable@chromium.org42c20792013-09-12 17:34:49 +0000977 # Construct new unified R= and TBR= lines.
978 r_names = []
979 tbr_names = []
980 for match in matches:
981 if not match:
982 continue
983 people = cleanup_list([match.group(2).strip()])
984 if match.group(1) == 'TBR':
985 tbr_names.extend(people)
986 else:
987 r_names.extend(people)
988 for name in r_names:
989 if name not in reviewers:
990 reviewers.append(name)
piman@chromium.org336f9122014-09-04 02:16:55 +0000991 if add_owners_tbr:
992 owners_db = owners.Database(change.RepositoryRoot(),
993 fopen=file, os_path=os.path, glob=glob.glob)
994 all_reviewers = set(tbr_names + reviewers)
995 missing_files = owners_db.files_not_covered_by(change.LocalPaths(),
996 all_reviewers)
997 tbr_names.extend(owners_db.reviewers_for(missing_files,
998 change.author_email))
agable@chromium.org42c20792013-09-12 17:34:49 +0000999 new_r_line = 'R=' + ', '.join(reviewers) if reviewers else None
1000 new_tbr_line = 'TBR=' + ', '.join(tbr_names) if tbr_names else None
1001
1002 # Put the new lines in the description where the old first R= line was.
1003 line_loc = next((i for i, match in enumerate(matches) if match), -1)
1004 if 0 <= line_loc < len(self._description_lines):
1005 if new_tbr_line:
1006 self._description_lines.insert(line_loc, new_tbr_line)
1007 if new_r_line:
1008 self._description_lines.insert(line_loc, new_r_line)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001009 else:
agable@chromium.org42c20792013-09-12 17:34:49 +00001010 if new_r_line:
1011 self.append_footer(new_r_line)
1012 if new_tbr_line:
1013 self.append_footer(new_tbr_line)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001014
1015 def prompt(self):
1016 """Asks the user to update the description."""
agable@chromium.org42c20792013-09-12 17:34:49 +00001017 self.set_description([
1018 '# Enter a description of the change.',
1019 '# This will be displayed on the codereview site.',
1020 '# The first line will also be used as the subject of the review.',
alancutter@chromium.orgbd1073e2013-06-01 00:34:38 +00001021 '#--------------------This line is 72 characters long'
agable@chromium.org42c20792013-09-12 17:34:49 +00001022 '--------------------',
1023 ] + self._description_lines)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001024
agable@chromium.org42c20792013-09-12 17:34:49 +00001025 regexp = re.compile(self.BUG_LINE)
1026 if not any((regexp.match(line) for line in self._description_lines)):
rmistry@google.com90752582014-01-14 21:04:50 +00001027 self.append_footer('BUG=%s' % settings.GetBugPrefix())
agable@chromium.org42c20792013-09-12 17:34:49 +00001028 content = gclient_utils.RunEditor(self.description, True,
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001029 git_editor=settings.GetGitEditor())
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001030 if not content:
1031 DieWithError('Running editor failed')
agable@chromium.org42c20792013-09-12 17:34:49 +00001032 lines = content.splitlines()
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001033
1034 # Strip off comments.
agable@chromium.org42c20792013-09-12 17:34:49 +00001035 clean_lines = [line.rstrip() for line in lines if not line.startswith('#')]
1036 if not clean_lines:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001037 DieWithError('No CL description, aborting')
agable@chromium.org42c20792013-09-12 17:34:49 +00001038 self.set_description(clean_lines)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001039
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001040 def append_footer(self, line):
agable@chromium.org42c20792013-09-12 17:34:49 +00001041 if self._description_lines:
1042 # Add an empty line if either the last line or the new line isn't a tag.
1043 last_line = self._description_lines[-1]
1044 if (not presubmit_support.Change.TAG_LINE_RE.match(last_line) or
1045 not presubmit_support.Change.TAG_LINE_RE.match(line)):
1046 self._description_lines.append('')
1047 self._description_lines.append(line)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001048
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001049 def get_reviewers(self):
1050 """Retrieves the list of reviewers."""
agable@chromium.org42c20792013-09-12 17:34:49 +00001051 matches = [re.match(self.R_LINE, line) for line in self._description_lines]
1052 reviewers = [match.group(2).strip() for match in matches if match]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001053 return cleanup_list(reviewers)
dpranke@chromium.org20254fc2011-03-22 18:28:59 +00001054
1055
maruel@chromium.orge52678e2013-04-26 18:34:44 +00001056def get_approving_reviewers(props):
1057 """Retrieves the reviewers that approved a CL from the issue properties with
1058 messages.
1059
1060 Note that the list may contain reviewers that are not committer, thus are not
1061 considered by the CQ.
1062 """
1063 return sorted(
1064 set(
1065 message['sender']
1066 for message in props['messages']
1067 if message['approval'] and message['sender'] in props['reviewers']
1068 )
1069 )
1070
1071
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001072def FindCodereviewSettingsFile(filename='codereview.settings'):
1073 """Finds the given file starting in the cwd and going up.
1074
1075 Only looks up to the top of the repository unless an
1076 'inherit-review-settings-ok' file exists in the root of the repository.
1077 """
1078 inherit_ok_file = 'inherit-review-settings-ok'
1079 cwd = os.getcwd()
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001080 root = settings.GetRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001081 if os.path.isfile(os.path.join(root, inherit_ok_file)):
1082 root = '/'
1083 while True:
1084 if filename in os.listdir(cwd):
1085 if os.path.isfile(os.path.join(cwd, filename)):
1086 return open(os.path.join(cwd, filename))
1087 if cwd == root:
1088 break
1089 cwd = os.path.dirname(cwd)
1090
1091
1092def LoadCodereviewSettingsFromFile(fileobj):
1093 """Parse a codereview.settings file and updates hooks."""
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001094 keyvals = gclient_utils.ParseCodereviewSettingsContent(fileobj.read())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001095
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001096 def SetProperty(name, setting, unset_error_ok=False):
1097 fullname = 'rietveld.' + name
1098 if setting in keyvals:
1099 RunGit(['config', fullname, keyvals[setting]])
1100 else:
1101 RunGit(['config', '--unset-all', fullname], error_ok=unset_error_ok)
1102
1103 SetProperty('server', 'CODE_REVIEW_SERVER')
1104 # Only server setting is required. Other settings can be absent.
1105 # In that case, we ignore errors raised during option deletion attempt.
1106 SetProperty('cc', 'CC_LIST', unset_error_ok=True)
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001107 SetProperty('private', 'PRIVATE', unset_error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001108 SetProperty('tree-status-url', 'STATUS', unset_error_ok=True)
1109 SetProperty('viewvc-url', 'VIEW_VC', unset_error_ok=True)
rmistry@google.com90752582014-01-14 21:04:50 +00001110 SetProperty('bug-prefix', 'BUG_PREFIX', unset_error_ok=True)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001111 SetProperty('cpplint-regex', 'LINT_REGEX', unset_error_ok=True)
1112 SetProperty('cpplint-ignore-regex', 'LINT_IGNORE_REGEX', unset_error_ok=True)
sheyang@chromium.org152cf832014-06-11 21:37:49 +00001113 SetProperty('project', 'PROJECT', unset_error_ok=True)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00001114 SetProperty('pending-ref-prefix', 'PENDING_REF_PREFIX', unset_error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001115
ukai@chromium.org7044efc2013-11-28 01:51:21 +00001116 if 'GERRIT_HOST' in keyvals:
ukai@chromium.orge8077812012-02-03 03:41:46 +00001117 RunGit(['config', 'gerrit.host', keyvals['GERRIT_HOST']])
ukai@chromium.orge8077812012-02-03 03:41:46 +00001118
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001119 if 'PUSH_URL_CONFIG' in keyvals and 'ORIGIN_URL_CONFIG' in keyvals:
1120 #should be of the form
1121 #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof
1122 #ORIGIN_URL_CONFIG: http://src.chromium.org/git
1123 RunGit(['config', keyvals['PUSH_URL_CONFIG'],
1124 keyvals['ORIGIN_URL_CONFIG']])
1125
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001126
joshua.lock@intel.com426f69b2012-08-02 23:41:49 +00001127def urlretrieve(source, destination):
1128 """urllib is broken for SSL connections via a proxy therefore we
1129 can't use urllib.urlretrieve()."""
1130 with open(destination, 'w') as f:
1131 f.write(urllib2.urlopen(source).read())
1132
1133
ukai@chromium.org712d6102013-11-27 00:52:58 +00001134def hasSheBang(fname):
1135 """Checks fname is a #! script."""
1136 with open(fname) as f:
1137 return f.read(2).startswith('#!')
1138
1139
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001140def DownloadHooks(force):
1141 """downloads hooks
1142
1143 Args:
1144 force: True to update hooks. False to install hooks if not present.
1145 """
1146 if not settings.GetIsGerrit():
1147 return
ukai@chromium.org712d6102013-11-27 00:52:58 +00001148 src = 'https://gerrit-review.googlesource.com/tools/hooks/commit-msg'
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001149 dst = os.path.join(settings.GetRoot(), '.git', 'hooks', 'commit-msg')
1150 if not os.access(dst, os.X_OK):
1151 if os.path.exists(dst):
1152 if not force:
1153 return
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001154 try:
joshua.lock@intel.com426f69b2012-08-02 23:41:49 +00001155 urlretrieve(src, dst)
ukai@chromium.org712d6102013-11-27 00:52:58 +00001156 if not hasSheBang(dst):
1157 DieWithError('Not a script: %s\n'
1158 'You need to download from\n%s\n'
1159 'into .git/hooks/commit-msg and '
1160 'chmod +x .git/hooks/commit-msg' % (dst, src))
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001161 os.chmod(dst, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
1162 except Exception:
1163 if os.path.exists(dst):
1164 os.remove(dst)
ukai@chromium.org712d6102013-11-27 00:52:58 +00001165 DieWithError('\nFailed to download hooks.\n'
1166 'You need to download from\n%s\n'
1167 'into .git/hooks/commit-msg and '
1168 'chmod +x .git/hooks/commit-msg' % src)
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001169
1170
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001171@subcommand.usage('[repo root containing codereview.settings]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001172def CMDconfig(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001173 """Edits configuration for this tree."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001174
pgervais@chromium.org87884cc2014-01-03 22:23:41 +00001175 parser.add_option('--activate-update', action='store_true',
1176 help='activate auto-updating [rietveld] section in '
1177 '.git/config')
1178 parser.add_option('--deactivate-update', action='store_true',
1179 help='deactivate auto-updating [rietveld] section in '
1180 '.git/config')
1181 options, args = parser.parse_args(args)
1182
1183 if options.deactivate_update:
1184 RunGit(['config', 'rietveld.autoupdate', 'false'])
1185 return
1186
1187 if options.activate_update:
1188 RunGit(['config', '--unset', 'rietveld.autoupdate'])
1189 return
1190
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001191 if len(args) == 0:
1192 GetCodereviewSettingsInteractively()
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001193 DownloadHooks(True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001194 return 0
1195
1196 url = args[0]
1197 if not url.endswith('codereview.settings'):
1198 url = os.path.join(url, 'codereview.settings')
1199
1200 # Load code review settings and download hooks (if available).
1201 LoadCodereviewSettingsFromFile(urllib2.urlopen(url))
ukai@chromium.org78c4b982012-02-14 02:20:26 +00001202 DownloadHooks(True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001203 return 0
1204
1205
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001206def CMDbaseurl(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001207 """Gets or sets base-url for this branch."""
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001208 branchref = RunGit(['symbolic-ref', 'HEAD']).strip()
1209 branch = ShortBranchName(branchref)
1210 _, args = parser.parse_args(args)
1211 if not args:
1212 print("Current base-url:")
1213 return RunGit(['config', 'branch.%s.base-url' % branch],
1214 error_ok=False).strip()
1215 else:
1216 print("Setting base-url to %s" % args[0])
1217 return RunGit(['config', 'branch.%s.base-url' % branch, args[0]],
1218 error_ok=False).strip()
1219
1220
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001221def CMDstatus(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001222 """Show status of changelists.
1223
1224 Colors are used to tell the state of the CL unless --fast is used:
jsbell@chromium.orgaeab41a2013-12-10 20:01:22 +00001225 - Red not sent for review or broken
1226 - Blue waiting for review
1227 - Yellow waiting for you to reply to review
1228 - Green LGTM'ed
1229 - Magenta in the commit queue
1230 - Cyan was committed, branch can be deleted
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001231
1232 Also see 'git cl comments'.
1233 """
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001234 parser.add_option('--field',
1235 help='print only specific field (desc|id|patch|url)')
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001236 parser.add_option('-f', '--fast', action='store_true',
1237 help='Do not retrieve review status')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001238 (options, args) = parser.parse_args(args)
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001239 if args:
1240 parser.error('Unsupported args: %s' % args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001241
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001242 if options.field:
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001243 cl = Changelist()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001244 if options.field.startswith('desc'):
1245 print cl.GetDescription()
1246 elif options.field == 'id':
1247 issueid = cl.GetIssue()
1248 if issueid:
1249 print issueid
1250 elif options.field == 'patch':
1251 patchset = cl.GetPatchset()
1252 if patchset:
1253 print patchset
1254 elif options.field == 'url':
1255 url = cl.GetIssueURL()
1256 if url:
1257 print url
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001258 return 0
1259
1260 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads'])
1261 if not branches:
1262 print('No local branch found.')
1263 return 0
1264
1265 changes = (Changelist(branchref=b) for b in branches.splitlines())
jrobbins@chromium.orga4c03052014-04-25 19:06:36 +00001266 branches = [c.GetBranch() for c in changes]
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001267 alignment = max(5, max(len(b) for b in branches))
1268 print 'Branches associated with reviews:'
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001269 # Adhoc thread pool to request data concurrently.
1270 output = Queue.Queue()
1271
1272 # Silence upload.py otherwise it becomes unweldly.
1273 upload.verbosity = 0
1274
1275 if not options.fast:
1276 def fetch(b):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001277 """Fetches information for an issue and returns (branch, issue, color)."""
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001278 c = Changelist(branchref=b)
1279 i = c.GetIssueURL()
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001280 props = {}
1281 r = None
1282 if i:
1283 try:
1284 props = c.GetIssueProperties()
1285 r = c.GetApprovingReviewers() if i else None
1286 except urllib2.HTTPError:
1287 # The issue probably doesn't exist anymore.
1288 i += ' (broken)'
1289
1290 msgs = props.get('messages') or []
1291
1292 if not i:
1293 color = Fore.WHITE
1294 elif props.get('closed'):
1295 # Issue is closed.
1296 color = Fore.CYAN
jsbell@chromium.orgaeab41a2013-12-10 20:01:22 +00001297 elif props.get('commit'):
1298 # Issue is in the commit queue.
1299 color = Fore.MAGENTA
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001300 elif r:
1301 # Was LGTM'ed.
1302 color = Fore.GREEN
1303 elif not msgs:
1304 # No message was sent.
1305 color = Fore.RED
1306 elif msgs[-1]['sender'] != props.get('owner_email'):
1307 color = Fore.YELLOW
1308 else:
1309 color = Fore.BLUE
1310 output.put((b, i, color))
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001311
jrobbins@chromium.orga4c03052014-04-25 19:06:36 +00001312 # Process one branch synchronously to work through authentication, then
1313 # spawn threads to process all the other branches in parallel.
1314 if branches:
1315 fetch(branches[0])
1316 threads = [
1317 threading.Thread(target=fetch, args=(b,)) for b in branches[1:]]
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001318 for t in threads:
1319 t.daemon = True
1320 t.start()
1321 else:
1322 # Do not use GetApprovingReviewers(), since it requires an HTTP request.
1323 for b in branches:
1324 c = Changelist(branchref=b)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001325 url = c.GetIssueURL()
1326 output.put((b, url, Fore.BLUE if url else Fore.WHITE))
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001327
1328 tmp = {}
1329 alignment = max(5, max(len(ShortBranchName(b)) for b in branches))
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001330 for branch in sorted(branches):
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001331 while branch not in tmp:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001332 b, i, color = output.get()
1333 tmp[b] = (i, color)
1334 issue, color = tmp.pop(branch)
maruel@chromium.org885f6512013-07-27 02:17:26 +00001335 reset = Fore.RESET
1336 if not sys.stdout.isatty():
1337 color = ''
1338 reset = ''
binji@chromium.orgc3d17dd2013-12-19 00:55:31 +00001339 print ' %*s : %s%s%s' % (
maruel@chromium.org885f6512013-07-27 02:17:26 +00001340 alignment, ShortBranchName(branch), color, issue, reset)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001341
maruel@chromium.orge25c75b2013-07-23 18:30:56 +00001342 cl = Changelist()
1343 print
1344 print 'Current branch:',
1345 if not cl.GetIssue():
1346 print 'no issue assigned.'
1347 return 0
1348 print cl.GetBranch()
1349 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL())
maruel@chromium.org85616e02014-07-28 15:37:55 +00001350 if not options.fast:
1351 print 'Issue description:'
1352 print cl.GetDescription(pretty=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001353 return 0
1354
1355
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001356def colorize_CMDstatus_doc():
1357 """To be called once in main() to add colors to git cl status help."""
1358 colors = [i for i in dir(Fore) if i[0].isupper()]
1359
1360 def colorize_line(line):
1361 for color in colors:
1362 if color in line.upper():
1363 # Extract whitespaces first and the leading '-'.
1364 indent = len(line) - len(line.lstrip(' ')) + 1
1365 return line[:indent] + getattr(Fore, color) + line[indent:] + Fore.RESET
1366 return line
1367
1368 lines = CMDstatus.__doc__.splitlines()
1369 CMDstatus.__doc__ = '\n'.join(colorize_line(l) for l in lines)
1370
1371
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001372@subcommand.usage('[issue_number]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001373def CMDissue(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001374 """Sets or displays the current code review issue number.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001375
1376 Pass issue number 0 to clear the current issue.
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001377 """
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00001378 _, args = parser.parse_args(args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001379
1380 cl = Changelist()
1381 if len(args) > 0:
1382 try:
1383 issue = int(args[0])
1384 except ValueError:
1385 DieWithError('Pass a number to set the issue or none to list it.\n'
1386 'Maybe you want to run git cl status?')
1387 cl.SetIssue(issue)
maruel@chromium.org52424302012-08-29 15:14:30 +00001388 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001389 return 0
1390
1391
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001392def CMDcomments(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001393 """Shows review comments of the current changelist."""
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001394 (_, args) = parser.parse_args(args)
1395 if args:
1396 parser.error('Unsupported argument: %s' % args)
1397
1398 cl = Changelist()
1399 if cl.GetIssue():
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001400 data = cl.GetIssueProperties()
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001401 for message in sorted(data['messages'], key=lambda x: x['date']):
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00001402 if message['disapproval']:
1403 color = Fore.RED
1404 elif message['approval']:
1405 color = Fore.GREEN
1406 elif message['sender'] == data['owner_email']:
1407 color = Fore.MAGENTA
1408 else:
1409 color = Fore.BLUE
1410 print '\n%s%s %s%s' % (
1411 color, message['date'].split('.', 1)[0], message['sender'],
1412 Fore.RESET)
maruel@chromium.org9977a2e2012-06-06 22:30:56 +00001413 if message['text'].strip():
1414 print '\n'.join(' ' + l for l in message['text'].splitlines())
1415 return 0
1416
1417
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001418def CMDdescription(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001419 """Brings up the editor for the current CL's description."""
rsesek@chromium.orgeec76592013-05-20 16:27:57 +00001420 cl = Changelist()
1421 if not cl.GetIssue():
1422 DieWithError('This branch has no associated changelist.')
1423 description = ChangeDescription(cl.GetDescription())
1424 description.prompt()
1425 cl.UpdateDescription(description.description)
1426 return 0
1427
1428
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001429def CreateDescriptionFromLog(args):
1430 """Pulls out the commit log to use as a base for the CL description."""
1431 log_args = []
1432 if len(args) == 1 and not args[0].endswith('.'):
1433 log_args = [args[0] + '..']
1434 elif len(args) == 1 and args[0].endswith('...'):
1435 log_args = [args[0][:-1]]
1436 elif len(args) == 2:
1437 log_args = [args[0] + '..' + args[1]]
1438 else:
1439 log_args = args[:] # Hope for the best!
maruel@chromium.org373af802012-05-25 21:07:33 +00001440 return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001441
1442
thestig@chromium.org44202a22014-03-11 19:22:18 +00001443def CMDlint(parser, args):
1444 """Runs cpplint on the current changelist."""
tzik@chromium.orgf204d4b2014-03-13 07:40:55 +00001445 parser.add_option('--filter', action='append', metavar='-x,+y',
1446 help='Comma-separated list of cpplint\'s category-filters')
1447 (options, args) = parser.parse_args(args)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001448
1449 # Access to a protected member _XX of a client class
1450 # pylint: disable=W0212
1451 try:
1452 import cpplint
1453 import cpplint_chromium
1454 except ImportError:
1455 print "Your depot_tools is missing cpplint.py and/or cpplint_chromium.py."
1456 return 1
1457
1458 # Change the current working directory before calling lint so that it
1459 # shows the correct base.
1460 previous_cwd = os.getcwd()
1461 os.chdir(settings.GetRoot())
1462 try:
1463 cl = Changelist()
1464 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
1465 files = [f.LocalPath() for f in change.AffectedFiles()]
thestig@chromium.org5839eb52014-05-30 16:20:51 +00001466 if not files:
1467 print "Cannot lint an empty CL"
1468 return 1
thestig@chromium.org44202a22014-03-11 19:22:18 +00001469
1470 # Process cpplints arguments if any.
tzik@chromium.orgf204d4b2014-03-13 07:40:55 +00001471 command = args + files
1472 if options.filter:
1473 command = ['--filter=' + ','.join(options.filter)] + command
1474 filenames = cpplint.ParseArguments(command)
thestig@chromium.org44202a22014-03-11 19:22:18 +00001475
1476 white_regex = re.compile(settings.GetLintRegex())
1477 black_regex = re.compile(settings.GetLintIgnoreRegex())
1478 extra_check_functions = [cpplint_chromium.CheckPointerDeclarationWhitespace]
1479 for filename in filenames:
1480 if white_regex.match(filename):
1481 if black_regex.match(filename):
1482 print "Ignoring file %s" % filename
1483 else:
1484 cpplint.ProcessFile(filename, cpplint._cpplint_state.verbose_level,
1485 extra_check_functions)
1486 else:
1487 print "Skipping file %s" % filename
1488 finally:
1489 os.chdir(previous_cwd)
1490 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count
1491 if cpplint._cpplint_state.error_count != 0:
1492 return 1
1493 return 0
1494
1495
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001496def CMDpresubmit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001497 """Runs presubmit tests on the current changelist."""
ilevy@chromium.org375a9022013-01-07 01:12:05 +00001498 parser.add_option('-u', '--upload', action='store_true',
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001499 help='Run upload hook instead of the push/dcommit hook')
ilevy@chromium.org375a9022013-01-07 01:12:05 +00001500 parser.add_option('-f', '--force', action='store_true',
sbc@chromium.org495ad152012-09-04 23:07:42 +00001501 help='Run checks even if tree is dirty')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001502 (options, args) = parser.parse_args(args)
1503
ukai@chromium.org259e4682012-10-25 07:36:33 +00001504 if not options.force and is_dirty_git_tree('presubmit'):
1505 print 'use --force to check even if tree is dirty.'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001506 return 1
1507
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001508 cl = Changelist()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001509 if args:
1510 base_branch = args[0]
1511 else:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00001512 # Default to diffing against the common ancestor of the upstream branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001513 base_branch = cl.GetCommonAncestorWithUpstream()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001514
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00001515 cl.RunHook(
1516 committing=not options.upload,
1517 may_prompt=False,
1518 verbose=options.verbose,
1519 change=cl.GetChange(base_branch, None))
dpranke@chromium.org0a2bb372011-03-25 01:16:22 +00001520 return 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001521
1522
sivachandra@chromium.orgaebe87f2012-10-22 20:34:21 +00001523def AddChangeIdToCommitMessage(options, args):
1524 """Re-commits using the current message, assumes the commit hook is in
1525 place.
1526 """
1527 log_desc = options.message or CreateDescriptionFromLog(args)
1528 git_command = ['commit', '--amend', '-m', log_desc]
1529 RunGit(git_command)
1530 new_log_desc = CreateDescriptionFromLog(args)
1531 if CHANGE_ID in new_log_desc:
1532 print 'git-cl: Added Change-Id to commit message.'
1533 else:
1534 print >> sys.stderr, 'ERROR: Gerrit commit-msg hook not available.'
1535
1536
piman@chromium.org336f9122014-09-04 02:16:55 +00001537def GerritUpload(options, args, cl, change):
ukai@chromium.orge8077812012-02-03 03:41:46 +00001538 """upload the current branch to gerrit."""
1539 # We assume the remote called "origin" is the one we want.
1540 # It is probably not worthwhile to support different workflows.
1541 remote = 'origin'
1542 branch = 'master'
1543 if options.target_branch:
1544 branch = options.target_branch
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001545
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001546 change_desc = ChangeDescription(
1547 options.message or CreateDescriptionFromLog(args))
1548 if not change_desc.description:
ukai@chromium.orge8077812012-02-03 03:41:46 +00001549 print "Description is empty; aborting."
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001550 return 1
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001551 if CHANGE_ID not in change_desc.description:
1552 AddChangeIdToCommitMessage(options, args)
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00001553
bauerb@chromium.org279c2182014-05-16 09:22:09 +00001554 commits = RunGit(['rev-list', '%s/%s..' % (remote, branch)]).splitlines()
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00001555 if len(commits) > 1:
1556 print('WARNING: This will upload %d commits. Run the following command '
1557 'to see which commits will be uploaded: ' % len(commits))
bauerb@chromium.org279c2182014-05-16 09:22:09 +00001558 print('git log %s/%s..' % (remote, branch))
bauerb@chromium.orgf75c2302014-05-01 08:19:30 +00001559 print('You can also use `git squash-branch` to squash these into a single'
1560 'commit.')
1561 ask_for_data('About to upload; enter to confirm.')
1562
piman@chromium.org336f9122014-09-04 02:16:55 +00001563 if options.reviewers or options.tbr_owners:
1564 change_desc.update_reviewers(options.reviewers, options.tbr_owners, change)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001565
ukai@chromium.orge8077812012-02-03 03:41:46 +00001566 receive_options = []
1567 cc = cl.GetCCList().split(',')
1568 if options.cc:
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00001569 cc.extend(options.cc)
ukai@chromium.orge8077812012-02-03 03:41:46 +00001570 cc = filter(None, cc)
1571 if cc:
1572 receive_options += ['--cc=' + email for email in cc]
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001573 if change_desc.get_reviewers():
1574 receive_options.extend(
1575 '--reviewer=' + email for email in change_desc.get_reviewers())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001576
ukai@chromium.orge8077812012-02-03 03:41:46 +00001577 git_command = ['push']
1578 if receive_options:
ukai@chromium.org19bbfa22012-02-03 16:18:11 +00001579 git_command.append('--receive-pack=git receive-pack %s' %
ukai@chromium.orge8077812012-02-03 03:41:46 +00001580 ' '.join(receive_options))
1581 git_command += [remote, 'HEAD:refs/for/' + branch]
1582 RunGit(git_command)
1583 # TODO(ukai): parse Change-Id: and set issue number?
1584 return 0
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001585
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001586
piman@chromium.org336f9122014-09-04 02:16:55 +00001587def RietveldUpload(options, args, cl, change):
ukai@chromium.orge8077812012-02-03 03:41:46 +00001588 """upload the patch to rietveld."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001589 upload_args = ['--assume_yes'] # Don't ask about untracked files.
1590 upload_args.extend(['--server', cl.GetRietveldServer()])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001591 if options.emulate_svn_auto_props:
1592 upload_args.append('--emulate_svn_auto_props')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001593
1594 change_desc = None
1595
pgervais@chromium.org91141372014-01-09 23:27:20 +00001596 if options.email is not None:
1597 upload_args.extend(['--email', options.email])
1598
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001599 if cl.GetIssue():
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00001600 if options.title:
1601 upload_args.extend(['--title', options.title])
rogerta@chromium.orgafadfca2013-05-29 14:15:53 +00001602 if options.message:
1603 upload_args.extend(['--message', options.message])
maruel@chromium.org52424302012-08-29 15:14:30 +00001604 upload_args.extend(['--issue', str(cl.GetIssue())])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001605 print ("This branch is associated with issue %s. "
1606 "Adding patch to that issue." % cl.GetIssue())
1607 else:
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00001608 if options.title:
1609 upload_args.extend(['--title', options.title])
rogerta@chromium.org43e34f02013-03-25 14:52:48 +00001610 message = options.title or options.message or CreateDescriptionFromLog(args)
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001611 change_desc = ChangeDescription(message)
piman@chromium.org336f9122014-09-04 02:16:55 +00001612 if options.reviewers or options.tbr_owners:
1613 change_desc.update_reviewers(options.reviewers,
1614 options.tbr_owners,
1615 change)
maruel@chromium.org71e12a92012-02-14 02:34:15 +00001616 if not options.force:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001617 change_desc.prompt()
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001618
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001619 if not change_desc.description:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001620 print "Description is empty; aborting."
1621 return 1
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001622
maruel@chromium.org71e12a92012-02-14 02:34:15 +00001623 upload_args.extend(['--message', change_desc.description])
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001624 if change_desc.get_reviewers():
1625 upload_args.append('--reviewers=' + ','.join(change_desc.get_reviewers()))
maruel@chromium.orga3353652011-11-30 14:26:57 +00001626 if options.send_mail:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001627 if not change_desc.get_reviewers():
maruel@chromium.orga3353652011-11-30 14:26:57 +00001628 DieWithError("Must specify reviewers to send email.")
1629 upload_args.append('--send_mail')
tyoshino@chromium.org99918ab2013-09-30 06:17:28 +00001630
1631 # We check this before applying rietveld.private assuming that in
1632 # rietveld.cc only addresses which we can send private CLs to are listed
1633 # if rietveld.private is set, and so we should ignore rietveld.cc only when
1634 # --private is specified explicitly on the command line.
1635 if options.private:
1636 logging.warn('rietveld.cc is ignored since private flag is specified. '
1637 'You need to review and add them manually if necessary.')
1638 cc = cl.GetCCListWithoutDefault()
1639 else:
1640 cc = cl.GetCCList()
1641 cc = ','.join(filter(None, (cc, ','.join(options.cc))))
maruel@chromium.orgb2a7c332011-02-25 20:30:37 +00001642 if cc:
1643 upload_args.extend(['--cc', cc])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001644
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001645 if options.private or settings.GetDefaultPrivateFlag() == "True":
1646 upload_args.append('--private')
1647
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00001648 upload_args.extend(['--git_similarity', str(options.similarity)])
iannucci@chromium.org79540052012-10-19 23:15:26 +00001649 if not options.find_copies:
1650 upload_args.extend(['--git_no_find_copies'])
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00001651
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001652 # Include the upstream repo's URL in the change -- this is useful for
1653 # projects that have their source spread across multiple repos.
kalmard@homejinni.com6b0051e2012-04-03 15:45:08 +00001654 remote_url = cl.GetGitBaseUrlFromConfig()
1655 if not remote_url:
1656 if settings.GetIsGitSvn():
1657 # URL is dependent on the current directory.
1658 data = RunGit(['svn', 'info'], cwd=settings.GetRoot())
1659 if data:
1660 keys = dict(line.split(': ', 1) for line in data.splitlines()
1661 if ': ' in line)
1662 remote_url = keys.get('URL', None)
1663 else:
1664 if cl.GetRemoteUrl() and '/' in cl.GetUpstreamBranch():
1665 remote_url = (cl.GetRemoteUrl() + '@'
1666 + cl.GetUpstreamBranch().split('/')[-1])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001667 if remote_url:
1668 upload_args.extend(['--base_url', remote_url])
1669
sheyang@chromium.org152cf832014-06-11 21:37:49 +00001670 project = settings.GetProject()
1671 if project:
1672 upload_args.extend(['--project', project])
1673
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001674 try:
ilevy@chromium.org82880192012-11-26 15:41:57 +00001675 upload_args = ['upload'] + upload_args + args
1676 logging.info('upload.RealMain(%s)', upload_args)
1677 issue, patchset = upload.RealMain(upload_args)
maruel@chromium.org911fce12013-07-29 23:01:13 +00001678 issue = int(issue)
1679 patchset = int(patchset)
maruel@chromium.org9ce0dff2011-04-04 17:56:50 +00001680 except KeyboardInterrupt:
1681 sys.exit(1)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001682 except:
1683 # If we got an exception after the user typed a description for their
1684 # change, back up the description before re-raising.
1685 if change_desc:
1686 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
1687 print '\nGot exception while uploading -- saving description to %s\n' \
1688 % backup_path
1689 backup_file = open(backup_path, 'w')
dpranke@chromium.org970c5222011-03-12 00:32:24 +00001690 backup_file.write(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001691 backup_file.close()
1692 raise
1693
1694 if not cl.GetIssue():
1695 cl.SetIssue(issue)
1696 cl.SetPatchset(patchset)
maruel@chromium.org27bb3872011-05-30 20:33:19 +00001697
1698 if options.use_commit_queue:
1699 cl.SetFlag('commit', '1')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001700 return 0
1701
1702
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00001703def cleanup_list(l):
1704 """Fixes a list so that comma separated items are put as individual items.
1705
1706 So that "--reviewers joe@c,john@c --reviewers joa@c" results in
1707 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']).
1708 """
1709 items = sum((i.split(',') for i in l), [])
1710 stripped_items = (i.strip() for i in items)
1711 return sorted(filter(None, stripped_items))
1712
1713
maruel@chromium.org0633fb42013-08-16 20:06:14 +00001714@subcommand.usage('[args to "git diff"]')
ukai@chromium.orge8077812012-02-03 03:41:46 +00001715def CMDupload(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001716 """Uploads the current changelist to codereview."""
ukai@chromium.orge8077812012-02-03 03:41:46 +00001717 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
1718 help='bypass upload presubmit hook')
brettw@chromium.orgb65c43c2013-06-10 22:04:49 +00001719 parser.add_option('--bypass-watchlists', action='store_true',
1720 dest='bypass_watchlists',
1721 help='bypass watchlists auto CC-ing reviewers')
ukai@chromium.orge8077812012-02-03 03:41:46 +00001722 parser.add_option('-f', action='store_true', dest='force',
1723 help="force yes to questions (don't prompt)")
rogerta@chromium.org420d3b82012-05-14 18:41:38 +00001724 parser.add_option('-m', dest='message', help='message for patchset')
1725 parser.add_option('-t', dest='title', help='title for patchset')
ukai@chromium.orge8077812012-02-03 03:41:46 +00001726 parser.add_option('-r', '--reviewers',
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00001727 action='append', default=[],
ukai@chromium.orge8077812012-02-03 03:41:46 +00001728 help='reviewer email addresses')
1729 parser.add_option('--cc',
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00001730 action='append', default=[],
ukai@chromium.orge8077812012-02-03 03:41:46 +00001731 help='cc email addresses')
adamk@chromium.org36f47302013-04-05 01:08:31 +00001732 parser.add_option('-s', '--send-mail', action='store_true',
ukai@chromium.orge8077812012-02-03 03:41:46 +00001733 help='send email to reviewer immediately')
pgervais@chromium.orgb9f27512014-08-08 15:52:33 +00001734 parser.add_option('--emulate_svn_auto_props',
1735 '--emulate-svn-auto-props',
1736 action="store_true",
ukai@chromium.orge8077812012-02-03 03:41:46 +00001737 dest="emulate_svn_auto_props",
1738 help="Emulate Subversion's auto properties feature.")
ukai@chromium.orge8077812012-02-03 03:41:46 +00001739 parser.add_option('-c', '--use-commit-queue', action='store_true',
1740 help='tell the commit queue to commit this patchset')
tyoshino@chromium.orgc1737d02013-05-29 14:17:28 +00001741 parser.add_option('--private', action='store_true',
1742 help='set the review private (rietveld only)')
ukai@chromium.org8ef7ab22012-11-28 04:24:52 +00001743 parser.add_option('--target_branch',
pgervais@chromium.orgb9f27512014-08-08 15:52:33 +00001744 '--target-branch',
ukai@chromium.org8ef7ab22012-11-28 04:24:52 +00001745 help='When uploading to gerrit, remote branch to '
1746 'use for CL. Default: master')
pgervais@chromium.org91141372014-01-09 23:27:20 +00001747 parser.add_option('--email', default=None,
1748 help='email address to use to connect to Rietveld')
piman@chromium.org336f9122014-09-04 02:16:55 +00001749 parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true',
1750 help='add a set of OWNERS to TBR')
pgervais@chromium.org91141372014-01-09 23:27:20 +00001751
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00001752 add_git_similarity(parser)
ukai@chromium.orge8077812012-02-03 03:41:46 +00001753 (options, args) = parser.parse_args(args)
1754
ukai@chromium.org8ef7ab22012-11-28 04:24:52 +00001755 if options.target_branch and not settings.GetIsGerrit():
1756 parser.error('Use --target_branch for non gerrit repository.')
1757
ukai@chromium.org259e4682012-10-25 07:36:33 +00001758 if is_dirty_git_tree('upload'):
ukai@chromium.orge8077812012-02-03 03:41:46 +00001759 return 1
1760
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00001761 options.reviewers = cleanup_list(options.reviewers)
1762 options.cc = cleanup_list(options.cc)
1763
ukai@chromium.orge8077812012-02-03 03:41:46 +00001764 cl = Changelist()
1765 if args:
1766 # TODO(ukai): is it ok for gerrit case?
1767 base_branch = args[0]
1768 else:
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00001769 # Default to diffing against common ancestor of upstream branch
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001770 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org5e07e062013-02-28 23:55:44 +00001771 args = [base_branch, 'HEAD']
ukai@chromium.orge8077812012-02-03 03:41:46 +00001772
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00001773 # Apply watchlists on upload.
1774 change = cl.GetChange(base_branch, None)
1775 watchlist = watchlists.Watchlists(change.RepositoryRoot())
1776 files = [f.LocalPath() for f in change.AffectedFiles()]
brettw@chromium.orgb65c43c2013-06-10 22:04:49 +00001777 if not options.bypass_watchlists:
1778 cl.SetWatchers(watchlist.GetWatchersForPaths(files))
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00001779
ukai@chromium.orge8077812012-02-03 03:41:46 +00001780 if not options.bypass_hooks:
piman@chromium.org336f9122014-09-04 02:16:55 +00001781 if options.reviewers or options.tbr_owners:
isherman@chromium.orgb5cded62014-03-25 17:47:57 +00001782 # Set the reviewer list now so that presubmit checks can access it.
1783 change_description = ChangeDescription(change.FullDescriptionText())
piman@chromium.org336f9122014-09-04 02:16:55 +00001784 change_description.update_reviewers(options.reviewers,
1785 options.tbr_owners,
1786 change)
isherman@chromium.orgb5cded62014-03-25 17:47:57 +00001787 change.SetDescriptionText(change_description.description)
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00001788 hook_results = cl.RunHook(committing=False,
ukai@chromium.orge8077812012-02-03 03:41:46 +00001789 may_prompt=not options.force,
1790 verbose=options.verbose,
ilevy@chromium.org051ad0e2013-03-04 21:57:34 +00001791 change=change)
ukai@chromium.orge8077812012-02-03 03:41:46 +00001792 if not hook_results.should_continue():
1793 return 1
1794 if not options.reviewers and hook_results.reviewers:
maruel@chromium.orgeb52a5c2013-04-10 23:17:09 +00001795 options.reviewers = hook_results.reviewers.split(',')
ukai@chromium.orge8077812012-02-03 03:41:46 +00001796
koz@chromium.org5974d7a2013-04-02 20:50:37 +00001797 if cl.GetIssue():
maruel@chromium.org1033efd2013-07-23 23:25:09 +00001798 latest_patchset = cl.GetMostRecentPatchset()
koz@chromium.org5974d7a2013-04-02 20:50:37 +00001799 local_patchset = cl.GetPatchset()
dmikurube@chromium.org07d149f2013-04-03 11:40:23 +00001800 if latest_patchset and local_patchset and local_patchset != latest_patchset:
koz@chromium.org5974d7a2013-04-02 20:50:37 +00001801 print ('The last upload made from this repository was patchset #%d but '
1802 'the most recent patchset on the server is #%d.'
1803 % (local_patchset, latest_patchset))
koz@chromium.orgc7192782013-04-09 23:28:46 +00001804 print ('Uploading will still work, but if you\'ve uploaded to this issue '
1805 'from another machine or branch the patch you\'re uploading now '
1806 'might not include those changes.')
koz@chromium.org5974d7a2013-04-02 20:50:37 +00001807 ask_for_data('About to upload; enter to confirm.')
1808
iannucci@chromium.org79540052012-10-19 23:15:26 +00001809 print_stats(options.similarity, options.find_copies, args)
ukai@chromium.orge8077812012-02-03 03:41:46 +00001810 if settings.GetIsGerrit():
piman@chromium.org336f9122014-09-04 02:16:55 +00001811 return GerritUpload(options, args, cl, change)
1812 ret = RietveldUpload(options, args, cl, change)
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00001813 if not ret:
rogerta@chromium.org4a6cd042013-04-12 15:40:42 +00001814 git_set_branch_value('last-upload-hash',
1815 RunGit(['rev-parse', 'HEAD']).strip())
rogerta@chromium.orgcaa16552013-03-18 20:45:05 +00001816
1817 return ret
ukai@chromium.orge8077812012-02-03 03:41:46 +00001818
1819
szager@chromium.org9bb85e22012-06-13 20:28:23 +00001820def IsSubmoduleMergeCommit(ref):
1821 # When submodules are added to the repo, we expect there to be a single
1822 # non-git-svn merge commit at remote HEAD with a signature comment.
1823 pattern = '^SVN changes up to revision [0-9]*$'
szager@chromium.orge84b7542012-06-15 21:26:58 +00001824 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref]
szager@chromium.org9bb85e22012-06-13 20:28:23 +00001825 return RunGit(cmd) != ''
1826
1827
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001828def SendUpstream(parser, args, cmd):
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00001829 """Common code for CMDland and CmdDCommit
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001830
iannucci@chromium.org5724c962014-04-11 09:32:56 +00001831 Squashes branch into a single commit.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001832 Updates changelog with metadata (e.g. pointer to review).
1833 Pushes/dcommits the code upstream.
1834 Updates review and closes.
1835 """
1836 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
1837 help='bypass upload presubmit hook')
1838 parser.add_option('-m', dest='message',
1839 help="override review description")
1840 parser.add_option('-f', action='store_true', dest='force',
1841 help="force yes to questions (don't prompt)")
1842 parser.add_option('-c', dest='contributor',
1843 help="external contributor for patch (appended to " +
1844 "description and used as author for git). Should be " +
1845 "formatted as 'First Last <email@example.com>'")
iannucci@chromium.org53937ba2012-10-02 18:20:43 +00001846 add_git_similarity(parser)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001847 (options, args) = parser.parse_args(args)
1848 cl = Changelist()
1849
iannucci@chromium.org5724c962014-04-11 09:32:56 +00001850 current = cl.GetBranch()
1851 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
1852 if not settings.GetIsGitSvn() and remote == '.':
1853 print
1854 print 'Attempting to push branch %r into another local branch!' % current
1855 print
1856 print 'Either reparent this branch on top of origin/master:'
1857 print ' git reparent-branch --root'
1858 print
1859 print 'OR run `git rebase-update` if you think the parent branch is already'
1860 print 'committed.'
1861 print
1862 print ' Current parent: %r' % upstream_branch
1863 return 1
1864
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00001865 if not args or cmd == 'land':
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001866 # Default to merging against our best guess of the upstream branch.
1867 args = [cl.GetUpstreamBranch()]
1868
maruel@chromium.org13f623c2011-07-22 16:02:23 +00001869 if options.contributor:
1870 if not re.match('^.*\s<\S+@\S+>$', options.contributor):
1871 print "Please provide contibutor as 'First Last <email@example.com>'"
1872 return 1
1873
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001874 base_branch = args[0]
szager@chromium.org9bb85e22012-06-13 20:28:23 +00001875 base_has_submodules = IsSubmoduleMergeCommit(base_branch)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001876
ukai@chromium.org259e4682012-10-25 07:36:33 +00001877 if is_dirty_git_tree(cmd):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001878 return 1
1879
1880 # This rev-list syntax means "show all commits not in my branch that
1881 # are in base_branch".
1882 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(),
1883 base_branch]).splitlines()
1884 if upstream_commits:
1885 print ('Base branch "%s" has %d commits '
1886 'not in this branch.' % (base_branch, len(upstream_commits)))
1887 print 'Run "git merge %s" before attempting to %s.' % (base_branch, cmd)
1888 return 1
1889
szager@chromium.org9bb85e22012-06-13 20:28:23 +00001890 # This is the revision `svn dcommit` will commit on top of.
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00001891 svn_head = None
1892 if cmd == 'dcommit' or base_has_submodules:
1893 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1',
1894 '--pretty=format:%H'])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00001895
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001896 if cmd == 'dcommit':
szager@chromium.org9bb85e22012-06-13 20:28:23 +00001897 # If the base_head is a submodule merge commit, the first parent of the
1898 # base_head should be a git-svn commit, which is what we're interested in.
1899 base_svn_head = base_branch
1900 if base_has_submodules:
1901 base_svn_head += '^1'
1902
1903 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001904 if extra_commits:
1905 print ('This branch has %d additional commits not upstreamed yet.'
1906 % len(extra_commits.splitlines()))
1907 print ('Upstream "%s" or rebase this branch on top of the upstream trunk '
1908 'before attempting to %s.' % (base_branch, cmd))
1909 return 1
1910
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00001911 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip()
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00001912 if not options.bypass_hooks:
maruel@chromium.org13f623c2011-07-22 16:02:23 +00001913 author = None
1914 if options.contributor:
1915 author = re.search(r'\<(.*)\>', options.contributor).group(1)
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00001916 hook_results = cl.RunHook(
1917 committing=True,
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00001918 may_prompt=not options.force,
1919 verbose=options.verbose,
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00001920 change=cl.GetChange(merge_base, author))
maruel@chromium.orgb0a63912012-01-17 18:10:16 +00001921 if not hook_results.should_continue():
1922 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001923
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00001924 # Check the tree status if the tree status URL is set.
1925 status = GetTreeStatus()
1926 if 'closed' == status:
1927 print('The tree is closed. Please wait for it to reopen. Use '
1928 '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
1929 return 1
1930 elif 'unknown' == status:
1931 print('Unable to determine tree status. Please verify manually and '
1932 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
1933 return 1
maruel@chromium.orgac637152012-01-16 14:19:54 +00001934 else:
1935 breakpad.SendStack(
1936 'GitClHooksBypassedCommit',
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00001937 'Issue %s/%s bypassed hook when committing (tree status was "%s")' %
1938 (cl.GetRietveldServer(), cl.GetIssue(), GetTreeStatus()),
maruel@chromium.org2e72bb12012-01-17 15:18:35 +00001939 verbose=False)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001940
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001941 change_desc = ChangeDescription(options.message)
1942 if not change_desc.description and cl.GetIssue():
1943 change_desc = ChangeDescription(cl.GetDescription())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001944
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001945 if not change_desc.description:
erg@chromium.org1a173982012-08-29 20:43:05 +00001946 if not cl.GetIssue() and options.bypass_hooks:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00001947 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base]))
erg@chromium.org1a173982012-08-29 20:43:05 +00001948 else:
1949 print 'No description set.'
1950 print 'Visit %s/edit to set it.' % (cl.GetIssueURL())
1951 return 1
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001952
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001953 # Keep a separate copy for the commit message, because the commit message
1954 # contains the link to the Rietveld issue, while the Rietveld message contains
1955 # the commit viewvc url.
maruel@chromium.orge52678e2013-04-26 18:34:44 +00001956 # Keep a separate copy for the commit message.
1957 if cl.GetIssue():
maruel@chromium.orgcf087782013-07-23 13:08:48 +00001958 change_desc.update_reviewers(cl.GetApprovingReviewers())
maruel@chromium.orge52678e2013-04-26 18:34:44 +00001959
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001960 commit_desc = ChangeDescription(change_desc.description)
maruel@chromium.orgcc73ad62011-07-06 17:39:26 +00001961 if cl.GetIssue():
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001962 commit_desc.append_footer('Review URL: %s' % cl.GetIssueURL())
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001963 if options.contributor:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00001964 commit_desc.append_footer('Patch from %s.' % options.contributor)
1965
agable@chromium.orgeec3ea32013-08-15 20:31:39 +00001966 print('Description:')
1967 print(commit_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001968
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00001969 branches = [merge_base, cl.GetBranchRef()]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001970 if not options.force:
iannucci@chromium.org79540052012-10-19 23:15:26 +00001971 print_stats(options.similarity, options.find_copies, branches)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001972
szager@chromium.org9bb85e22012-06-13 20:28:23 +00001973 # We want to squash all this branch's commits into one commit with the proper
1974 # description. We do this by doing a "reset --soft" to the base branch (which
1975 # keeps the working copy the same), then dcommitting that. If origin/master
1976 # has a submodule merge commit, we'll also need to cherry-pick the squashed
1977 # commit onto a branch based on the git-svn head.
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001978 MERGE_BRANCH = 'git-cl-commit'
szager@chromium.org9bb85e22012-06-13 20:28:23 +00001979 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
1980 # Delete the branches if they exist.
1981 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]:
1982 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch]
1983 result = RunGitWithCode(showref_cmd)
1984 if result[0] == 0:
1985 RunGit(['branch', '-D', branch])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001986
1987 # We might be in a directory that's present in this branch but not in the
1988 # trunk. Move up to the top of the tree so that git commands that expect a
1989 # valid CWD won't fail after we check out the merge branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00001990 rel_base_path = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00001991 if rel_base_path:
1992 os.chdir(rel_base_path)
1993
1994 # Stuff our change into the merge branch.
1995 # We wrap in a try...finally block so if anything goes wrong,
1996 # we clean up the branches.
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00001997 retcode = -1
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00001998 pushed_to_pending = False
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00001999 pending_ref = None
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002000 revision = None
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002001 try:
bauerb@chromium.orgb4a75c42011-03-08 08:35:38 +00002002 RunGit(['checkout', '-q', '-b', MERGE_BRANCH])
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002003 RunGit(['reset', '--soft', merge_base])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002004 if options.contributor:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002005 RunGit(
2006 [
2007 'commit', '--author', options.contributor,
2008 '-m', commit_desc.description,
2009 ])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002010 else:
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002011 RunGit(['commit', '-m', commit_desc.description])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002012 if base_has_submodules:
2013 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip()
2014 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head])
2015 RunGit(['checkout', CHERRY_PICK_BRANCH])
2016 RunGit(['cherry-pick', cherry_pick_commit])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002017 if cmd == 'land':
ilevy@chromium.org0f58fa82012-11-05 01:45:20 +00002018 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002019 pending_prefix = settings.GetPendingRefPrefix()
2020 if not pending_prefix or branch.startswith(pending_prefix):
2021 # If not using refs/pending/heads/* at all, or target ref is already set
2022 # to pending, then push to the target ref directly.
2023 retcode, output = RunGitWithCode(
2024 ['push', '--porcelain', remote, 'HEAD:%s' % branch])
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002025 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002026 else:
2027 # Cherry-pick the change on top of pending ref and then push it.
2028 assert branch.startswith('refs/'), branch
2029 assert pending_prefix[-1] == '/', pending_prefix
2030 pending_ref = pending_prefix + branch[len('refs/'):]
2031 retcode, output = PushToGitPending(remote, pending_ref, branch)
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002032 pushed_to_pending = (retcode == 0)
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002033 if retcode == 0:
2034 revision = RunGit(['rev-parse', 'HEAD']).strip()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002035 else:
2036 # dcommit the merge branch.
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002037 _, output = RunGitWithCode(['svn', 'dcommit',
2038 '-C%s' % options.similarity,
2039 '--no-rebase', '--rmdir'])
2040 if 'Committed r' in output:
2041 revision = re.match(
2042 '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1)
2043 logging.debug(output)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002044 finally:
2045 # And then swap back to the original branch and clean up.
2046 RunGit(['checkout', '-q', cl.GetBranch()])
2047 RunGit(['branch', '-D', MERGE_BRANCH])
szager@chromium.org9bb85e22012-06-13 20:28:23 +00002048 if base_has_submodules:
2049 RunGit(['branch', '-D', CHERRY_PICK_BRANCH])
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002050
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002051 if not revision:
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002052 print 'Failed to push. If this persists, please file a bug.'
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002053 return 1
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002054
2055 if pushed_to_pending:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002056 try:
2057 revision = WaitForRealCommit(remote, revision, base_branch, branch)
2058 # We set pushed_to_pending to False, since it made it all the way to the
2059 # real ref.
2060 pushed_to_pending = False
2061 except KeyboardInterrupt:
2062 pass
2063
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002064 if cl.GetIssue():
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002065 to_pending = ' to pending queue' if pushed_to_pending else ''
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002066 viewvc_url = settings.GetViewVCUrl()
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002067 if not to_pending:
2068 if viewvc_url and revision:
2069 change_desc.append_footer(
2070 'Committed: %s%s' % (viewvc_url, revision))
2071 elif revision:
2072 change_desc.append_footer('Committed: %s' % (revision,))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002073 print ('Closing issue '
2074 '(you may be prompted for your codereview password)...')
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002075 cl.UpdateDescription(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002076 cl.CloseIssue()
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002077 props = cl.GetIssueProperties()
sadrul@chromium.org34b5d822013-02-18 01:39:24 +00002078 patch_num = len(props['patchsets'])
rmistry@google.com52d224a2014-08-27 14:44:41 +00002079 comment = "Committed patchset #%d (id:%d)%s manually as %s" % (
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002080 patch_num, props['patchsets'][-1], to_pending, revision[:7])
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002081 if options.bypass_hooks:
2082 comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.'
2083 else:
2084 comment += ' (presubmit successful).'
iannucci@chromium.orgb85a3162013-01-26 01:11:13 +00002085 cl.RpcServer().add_comment(cl.GetIssue(), comment)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002086 cl.SetIssue(None)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002087
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002088 if pushed_to_pending:
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002089 _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
2090 print 'The commit is in the pending queue (%s).' % pending_ref
2091 print (
2092 'It will show up on %s in ~1 min, once it gets Cr-Commit-Position '
2093 'footer.' % branch)
2094
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002095 hook = POSTUPSTREAM_HOOK_PATTERN % cmd
2096 if os.path.isfile(hook):
2097 RunCommand([hook, merge_base], error_ok=True)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002098
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002099 return 0
2100
2101
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002102def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
2103 print
2104 print 'Waiting for commit to be landed on %s...' % real_ref
2105 print '(If you are impatient, you may Ctrl-C once without harm)'
2106 target_tree = RunGit(['rev-parse', '%s:' % pushed_commit]).strip()
2107 current_rev = RunGit(['rev-parse', local_base_ref]).strip()
2108
2109 loop = 0
2110 while True:
2111 sys.stdout.write('fetching (%d)... \r' % loop)
2112 sys.stdout.flush()
2113 loop += 1
2114
2115 RunGit(['retry', 'fetch', remote, real_ref], stderr=subprocess2.VOID)
2116 to_rev = RunGit(['rev-parse', 'FETCH_HEAD']).strip()
2117 commits = RunGit(['rev-list', '%s..%s' % (current_rev, to_rev)])
2118 for commit in commits.splitlines():
2119 if RunGit(['rev-parse', '%s:' % commit]).strip() == target_tree:
2120 print 'Found commit on %s' % real_ref
2121 return commit
2122
2123 current_rev = to_rev
2124
2125
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002126def PushToGitPending(remote, pending_ref, upstream_ref):
2127 """Fetches pending_ref, cherry-picks current HEAD on top of it, pushes.
2128
2129 Returns:
2130 (retcode of last operation, output log of last operation).
2131 """
2132 assert pending_ref.startswith('refs/'), pending_ref
2133 local_pending_ref = 'refs/git-cl/' + pending_ref[len('refs/'):]
2134 cherry = RunGit(['rev-parse', 'HEAD']).strip()
2135 code = 0
2136 out = ''
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002137 max_attempts = 3
2138 attempts_left = max_attempts
2139 while attempts_left:
2140 if attempts_left != max_attempts:
2141 print 'Retrying, %d attempts left...' % (attempts_left - 1,)
2142 attempts_left -= 1
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002143
2144 # Fetch. Retry fetch errors.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002145 print 'Fetching pending ref %s...' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002146 code, out = RunGitWithCode(
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002147 ['retry', 'fetch', remote, '+%s:%s' % (pending_ref, local_pending_ref)])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002148 if code:
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002149 print 'Fetch failed with exit code %d.' % code
2150 if out.strip():
2151 print out.strip()
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002152 continue
2153
2154 # Try to cherry pick. Abort on merge conflicts.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002155 print 'Cherry-picking commit on top of pending ref...'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002156 RunGitWithCode(['checkout', local_pending_ref], suppress_stderr=True)
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002157 code, out = RunGitWithCode(['cherry-pick', cherry])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002158 if code:
2159 print (
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002160 'Your patch doesn\'t apply cleanly to ref \'%s\', '
2161 'the following files have merge conflicts:' % pending_ref)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002162 print RunGit(['diff', '--name-status', '--diff-filter=U']).strip()
2163 print 'Please rebase your patch and try again.'
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002164 RunGitWithCode(['cherry-pick', '--abort'])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002165 return code, out
2166
2167 # Applied cleanly, try to push now. Retry on error (flake or non-ff push).
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002168 print 'Pushing commit to %s... It can take a while.' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002169 code, out = RunGitWithCode(
2170 ['retry', 'push', '--porcelain', remote, 'HEAD:%s' % pending_ref])
2171 if code == 0:
2172 # Success.
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002173 print 'Commit pushed to pending ref successfully!'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002174 return code, out
2175
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002176 print 'Push failed with exit code %d.' % code
2177 if out.strip():
2178 print out.strip()
2179 if IsFatalPushFailure(out):
2180 print (
2181 'Fatal push error. Make sure your .netrc credentials and git '
2182 'user.email are correct and you have push access to the repo.')
2183 return code, out
2184
2185 print 'All attempts to push to pending ref failed.'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002186 return code, out
2187
2188
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002189def IsFatalPushFailure(push_stdout):
2190 """True if retrying push won't help."""
2191 return '(prohibited by Gerrit)' in push_stdout
2192
2193
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002194@subcommand.usage('[upstream branch to apply against]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002195def CMDdcommit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002196 """Commits the current changelist via git-svn."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002197 if not settings.GetIsGitSvn():
thakis@chromium.orgcde3bb62011-01-20 01:16:14 +00002198 message = """This doesn't appear to be an SVN repository.
2199If your project has a git mirror with an upstream SVN master, you probably need
2200to run 'git svn init', see your project's git mirror documentation.
2201If your project has a true writeable upstream repository, you probably want
mark@chromium.org671c7a32014-07-31 17:09:36 +00002202to run 'git cl land' instead.
thakis@chromium.orgcde3bb62011-01-20 01:16:14 +00002203Choose wisely, if you get this wrong, your commit might appear to succeed but
2204will instead be silently ignored."""
2205 print(message)
maruel@chromium.org90541732011-04-01 17:54:18 +00002206 ask_for_data('[Press enter to dcommit or ctrl-C to quit]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002207 return SendUpstream(parser, args, 'dcommit')
2208
2209
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002210@subcommand.usage('[upstream branch to apply against]')
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00002211def CMDland(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002212 """Commits the current changelist via git."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002213 if settings.GetIsGitSvn():
2214 print('This appears to be an SVN repository.')
2215 print('Are you sure you didn\'t mean \'git cl dcommit\'?')
maruel@chromium.org90541732011-04-01 17:54:18 +00002216 ask_for_data('[Press enter to push or ctrl-C to quit]')
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002217 return SendUpstream(parser, args, 'land')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002218
2219
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002220@subcommand.usage('<patch url or issue id>')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002221def CMDpatch(parser, args):
marq@chromium.orge5e59002013-10-02 23:21:25 +00002222 """Patches in a code review."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002223 parser.add_option('-b', dest='newbranch',
2224 help='create a new branch off trunk for the patch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002225 parser.add_option('-f', '--force', action='store_true',
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002226 help='with -b, clobber any existing branch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002227 parser.add_option('-d', '--directory', action='store', metavar='DIR',
2228 help='Change to the directory DIR immediately, '
2229 'before doing anything else.')
2230 parser.add_option('--reject', action='store_true',
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00002231 help='failed patches spew .rej files rather than '
2232 'attempting a 3-way merge')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002233 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit',
2234 help="don't commit after patch applies")
2235 (options, args) = parser.parse_args(args)
2236 if len(args) != 1:
2237 parser.print_help()
2238 return 1
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002239 issue_arg = args[0]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002240
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002241 # TODO(maruel): Use apply_issue.py
ukai@chromium.orge8077812012-02-03 03:41:46 +00002242 # TODO(ukai): use gerrit-cherry-pick for gerrit repository?
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002243
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002244 if options.newbranch:
2245 if options.force:
2246 RunGit(['branch', '-D', options.newbranch],
2247 stderr=subprocess2.PIPE, error_ok=True)
2248 RunGit(['checkout', '-b', options.newbranch,
2249 Changelist().GetUpstreamBranch()])
2250
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002251 return PatchIssue(issue_arg, options.reject, options.nocommit,
2252 options.directory)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002253
2254
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002255def PatchIssue(issue_arg, reject, nocommit, directory):
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002256 if type(issue_arg) is int or issue_arg.isdigit():
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002257 # Input is an issue id. Figure out the URL.
maruel@chromium.org52424302012-08-29 15:14:30 +00002258 issue = int(issue_arg)
jochen@chromium.orga26e0472013-07-24 10:25:01 +00002259 cl = Changelist(issue=issue)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002260 patchset = cl.GetMostRecentPatchset()
binji@chromium.org0281f522012-09-14 13:37:59 +00002261 patch_data = cl.GetPatchSetDiff(issue, patchset)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002262 else:
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00002263 # Assume it's a URL to the patch. Default to https.
2264 issue_url = gclient_utils.UpgradeToHttps(issue_arg)
binji@chromium.org0281f522012-09-14 13:37:59 +00002265 match = re.match(r'.*?/issue(\d+)_(\d+).diff', issue_url)
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002266 if not match:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002267 DieWithError('Must pass an issue ID or full URL for '
2268 '\'Download raw patch set\'')
maruel@chromium.org52424302012-08-29 15:14:30 +00002269 issue = int(match.group(1))
binji@chromium.org0281f522012-09-14 13:37:59 +00002270 patchset = int(match.group(2))
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002271 patch_data = urllib2.urlopen(issue_arg).read()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002272
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002273 # Switch up to the top-level directory, if necessary, in preparation for
2274 # applying the patch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002275 top = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002276 if top:
2277 os.chdir(top)
2278
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002279 # Git patches have a/ at the beginning of source paths. We strip that out
2280 # with a sed script rather than the -p flag to patch so we can feed either
2281 # Git or svn-style patches into the same apply command.
2282 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002283 try:
2284 patch_data = subprocess2.check_output(
2285 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
2286 except subprocess2.CalledProcessError:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002287 DieWithError('Git patch mungling failed.')
2288 logging.info(patch_data)
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002289
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002290 # We use "git apply" to apply the patch instead of "patch" so that we can
2291 # pick up file adds.
2292 # The --index flag means: also insert into the index (so we catch adds).
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002293 cmd = ['git', 'apply', '--index', '-p0']
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002294 if directory:
2295 cmd.extend(('--directory', directory))
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002296 if reject:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002297 cmd.append('--reject')
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00002298 elif IsGitVersionAtLeast('1.7.12'):
2299 cmd.append('--3way')
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002300 try:
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002301 subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002302 stdin=patch_data, stdout=subprocess2.VOID)
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002303 except subprocess2.CalledProcessError:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002304 DieWithError('Failed to apply the patch')
2305
2306 # If we had an issue, commit the current state and register the issue.
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002307 if not nocommit:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002308 RunGit(['commit', '-m', 'patch from issue %s' % issue])
2309 cl = Changelist()
2310 cl.SetIssue(issue)
binji@chromium.org0281f522012-09-14 13:37:59 +00002311 cl.SetPatchset(patchset)
pdr@chromium.org98ca6622013-04-09 20:58:40 +00002312 print "Committed patch locally."
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002313 else:
2314 print "Patch applied to index."
2315 return 0
2316
2317
2318def CMDrebase(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002319 """Rebases current branch on top of svn repo."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002320 # Provide a wrapper for git svn rebase to help avoid accidental
2321 # git svn dcommit.
2322 # It's the only command that doesn't use parser at all since we just defer
2323 # execution to git-svn.
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002324
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002325 return RunGitWithCode(['svn', 'rebase'] + args)[1]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002326
2327
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002328def GetTreeStatus(url=None):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002329 """Fetches the tree status and returns either 'open', 'closed',
2330 'unknown' or 'unset'."""
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002331 url = url or settings.GetTreeStatusUrl(error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002332 if url:
2333 status = urllib2.urlopen(url).read().lower()
2334 if status.find('closed') != -1 or status == '0':
2335 return 'closed'
2336 elif status.find('open') != -1 or status == '1':
2337 return 'open'
2338 return 'unknown'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002339 return 'unset'
2340
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002341
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002342def GetTreeStatusReason():
2343 """Fetches the tree status from a json url and returns the message
2344 with the reason for the tree to be opened or closed."""
msb@chromium.orgbf1a7ba2011-02-01 16:21:46 +00002345 url = settings.GetTreeStatusUrl()
2346 json_url = urlparse.urljoin(url, '/current?format=json')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002347 connection = urllib2.urlopen(json_url)
2348 status = json.loads(connection.read())
2349 connection.close()
2350 return status['message']
2351
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002352
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002353def GetBuilderMaster(bot_list):
2354 """For a given builder, fetch the master from AE if available."""
2355 map_url = 'https://builders-map.appspot.com/'
2356 try:
2357 master_map = json.load(urllib2.urlopen(map_url))
2358 except urllib2.URLError as e:
2359 return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' %
2360 (map_url, e))
2361 except ValueError as e:
2362 return None, ('Invalid json string from %s. Error: %s.' % (map_url, e))
2363 if not master_map:
2364 return None, 'Failed to build master map.'
2365
2366 result_master = ''
2367 for bot in bot_list:
2368 builder = bot.split(':', 1)[0]
2369 master_list = master_map.get(builder, [])
2370 if not master_list:
2371 return None, ('No matching master for builder %s.' % builder)
2372 elif len(master_list) > 1:
2373 return None, ('The builder name %s exists in multiple masters %s.' %
2374 (builder, master_list))
2375 else:
2376 cur_master = master_list[0]
2377 if not result_master:
2378 result_master = cur_master
2379 elif result_master != cur_master:
2380 return None, 'The builders do not belong to the same master.'
2381 return result_master, None
2382
2383
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002384def CMDtree(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002385 """Shows the status of the tree."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002386 _, args = parser.parse_args(args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002387 status = GetTreeStatus()
2388 if 'unset' == status:
2389 print 'You must configure your tree status URL by running "git cl config".'
2390 return 2
2391
2392 print "The tree is %s" % status
2393 print
2394 print GetTreeStatusReason()
2395 if status != 'open':
2396 return 1
2397 return 0
2398
2399
maruel@chromium.org15192402012-09-06 12:38:29 +00002400def CMDtry(parser, args):
2401 """Triggers a try job through Rietveld."""
2402 group = optparse.OptionGroup(parser, "Try job options")
2403 group.add_option(
2404 "-b", "--bot", action="append",
2405 help=("IMPORTANT: specify ONE builder per --bot flag. Use it multiple "
2406 "times to specify multiple builders. ex: "
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002407 "'-b win_rel:ui_tests,webkit_unit_tests -b win_layout'. See "
maruel@chromium.org15192402012-09-06 12:38:29 +00002408 "the try server waterfall for the builders name and the tests "
2409 "available. Can also be used to specify gtest_filter, e.g. "
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002410 "-b win_rel:base_unittests:ValuesTest.*Value"))
maruel@chromium.org15192402012-09-06 12:38:29 +00002411 group.add_option(
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002412 "-m", "--master", default='',
iannucci@chromium.org9e849272014-04-04 00:31:55 +00002413 help=("Specify a try master where to run the tries."))
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002414 group.add_option(
maruel@chromium.org15192402012-09-06 12:38:29 +00002415 "-r", "--revision",
2416 help="Revision to use for the try job; default: the "
2417 "revision will be determined by the try server; see "
2418 "its waterfall for more info")
2419 group.add_option(
2420 "-c", "--clobber", action="store_true", default=False,
2421 help="Force a clobber before building; e.g. don't do an "
2422 "incremental build")
2423 group.add_option(
2424 "--project",
2425 help="Override which project to use. Projects are defined "
2426 "server-side to define what default bot set to use")
2427 group.add_option(
2428 "-t", "--testfilter", action="append", default=[],
2429 help=("Apply a testfilter to all the selected builders. Unless the "
2430 "builders configurations are similar, use multiple "
2431 "--bot <builder>:<test> arguments."))
2432 group.add_option(
2433 "-n", "--name", help="Try job name; default to current branch name")
2434 parser.add_option_group(group)
2435 options, args = parser.parse_args(args)
2436
2437 if args:
2438 parser.error('Unknown arguments: %s' % args)
2439
2440 cl = Changelist()
2441 if not cl.GetIssue():
2442 parser.error('Need to upload first')
2443
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002444 props = cl.GetIssueProperties()
agable@chromium.org787e3062014-08-20 16:31:19 +00002445 if props.get('closed'):
2446 parser.error('Cannot send tryjobs for a closed CL')
2447
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002448 if props.get('private'):
2449 parser.error('Cannot use trybots with private issue')
2450
maruel@chromium.org15192402012-09-06 12:38:29 +00002451 if not options.name:
2452 options.name = cl.GetBranch()
2453
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00002454 if options.bot and not options.master:
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002455 options.master, err_msg = GetBuilderMaster(options.bot)
2456 if err_msg:
2457 parser.error('Tryserver master cannot be found because: %s\n'
2458 'Please manually specify the tryserver master'
2459 ', e.g. "-m tryserver.chromium.linux".' % err_msg)
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00002460
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002461 def GetMasterMap():
2462 # Process --bot and --testfilter.
2463 if not options.bot:
2464 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
maruel@chromium.org15192402012-09-06 12:38:29 +00002465
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002466 # Get try masters from PRESUBMIT.py files.
2467 masters = presubmit_support.DoGetTryMasters(
2468 change,
2469 change.LocalPaths(),
2470 settings.GetRoot(),
2471 None,
2472 None,
2473 options.verbose,
2474 sys.stdout)
2475 if masters:
2476 return masters
stip@chromium.org43064fd2013-12-18 20:07:44 +00002477
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002478 # Fall back to deprecated method: get try slaves from PRESUBMIT.py files.
2479 options.bot = presubmit_support.DoGetTrySlaves(
2480 change,
2481 change.LocalPaths(),
2482 settings.GetRoot(),
2483 None,
2484 None,
2485 options.verbose,
2486 sys.stdout)
2487 if not options.bot:
2488 parser.error('No default try builder to try, use --bot')
maruel@chromium.org15192402012-09-06 12:38:29 +00002489
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002490 builders_and_tests = {}
2491 # TODO(machenbach): The old style command-line options don't support
2492 # multiple try masters yet.
2493 old_style = filter(lambda x: isinstance(x, basestring), options.bot)
2494 new_style = filter(lambda x: isinstance(x, tuple), options.bot)
2495
2496 for bot in old_style:
2497 if ':' in bot:
2498 builder, tests = bot.split(':', 1)
2499 builders_and_tests.setdefault(builder, []).extend(tests.split(','))
2500 elif ',' in bot:
2501 parser.error('Specify one bot per --bot flag')
2502 else:
2503 builders_and_tests.setdefault(bot, []).append('defaulttests')
2504
2505 for bot, tests in new_style:
2506 builders_and_tests.setdefault(bot, []).extend(tests)
2507
2508 # Return a master map with one master to be backwards compatible. The
2509 # master name defaults to an empty string, which will cause the master
2510 # not to be set on rietveld (deprecated).
2511 return {options.master: builders_and_tests}
2512
2513 masters = GetMasterMap()
stip@chromium.org43064fd2013-12-18 20:07:44 +00002514
maruel@chromium.org15192402012-09-06 12:38:29 +00002515 if options.testfilter:
2516 forced_tests = sum((t.split(',') for t in options.testfilter), [])
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002517 masters = dict((master, dict(
2518 (b, forced_tests) for b, t in slaves.iteritems()
2519 if t != ['compile'])) for master, slaves in masters.iteritems())
maruel@chromium.org15192402012-09-06 12:38:29 +00002520
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002521 for builders in masters.itervalues():
2522 if any('triggered' in b for b in builders):
2523 print >> sys.stderr, (
2524 'ERROR You are trying to send a job to a triggered bot. This type of'
2525 ' bot requires an\ninitial job from a parent (usually a builder). '
2526 'Instead send your job to the parent.\n'
2527 'Bot list: %s' % builders)
2528 return 1
ilevy@chromium.orgf3b21232012-09-24 20:48:55 +00002529
ilevy@chromium.org36e420b2013-08-06 23:21:12 +00002530 patchset = cl.GetMostRecentPatchset()
2531 if patchset and patchset != cl.GetPatchset():
2532 print(
2533 '\nWARNING Mismatch between local config and server. Did a previous '
2534 'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
2535 'Continuing using\npatchset %s.\n' % patchset)
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002536 try:
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002537 cl.RpcServer().trigger_distributed_try_jobs(
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002538 cl.GetIssue(), patchset, options.name, options.clobber,
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002539 options.revision, masters)
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002540 except urllib2.HTTPError, e:
2541 if e.code == 404:
2542 print('404 from rietveld; '
2543 'did you mean to use "git try" instead of "git cl try"?')
2544 return 1
maruel@chromium.org072d94b2012-09-20 19:20:08 +00002545 print('Tried jobs on:')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002546
2547 for (master, builders) in masters.iteritems():
2548 if master:
2549 print 'Master: %s' % master
2550 length = max(len(builder) for builder in builders)
2551 for builder in sorted(builders):
2552 print ' %*s: %s' % (length, builder, ','.join(builders[builder]))
maruel@chromium.org15192402012-09-06 12:38:29 +00002553 return 0
2554
2555
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002556@subcommand.usage('[new upstream branch]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002557def CMDupstream(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002558 """Prints or sets the name of the upstream branch, if any."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002559 _, args = parser.parse_args(args)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002560 if len(args) > 1:
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002561 parser.error('Unrecognized args: %s' % ' '.join(args))
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002562
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002563 cl = Changelist()
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002564 if args:
2565 # One arg means set upstream branch.
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00002566 branch = cl.GetBranch()
2567 RunGit(['branch', '--set-upstream', branch, args[0]])
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002568 cl = Changelist()
2569 print "Upstream branch set to " + cl.GetUpstreamBranch()
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00002570
2571 # Clear configured merge-base, if there is one.
2572 git_common.remove_merge_base(branch)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002573 else:
2574 print cl.GetUpstreamBranch()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002575 return 0
2576
2577
thestig@chromium.org00858c82013-12-02 23:08:03 +00002578def CMDweb(parser, args):
2579 """Opens the current CL in the web browser."""
2580 _, args = parser.parse_args(args)
2581 if args:
2582 parser.error('Unrecognized args: %s' % ' '.join(args))
2583
2584 issue_url = Changelist().GetIssueURL()
2585 if not issue_url:
2586 print >> sys.stderr, 'ERROR No issue to open'
2587 return 1
2588
2589 webbrowser.open(issue_url)
2590 return 0
2591
2592
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002593def CMDset_commit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002594 """Sets the commit bit to trigger the Commit Queue."""
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002595 _, args = parser.parse_args(args)
2596 if args:
2597 parser.error('Unrecognized args: %s' % ' '.join(args))
2598 cl = Changelist()
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002599 props = cl.GetIssueProperties()
2600 if props.get('private'):
2601 parser.error('Cannot set commit on private issue')
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002602 cl.SetFlag('commit', '1')
2603 return 0
2604
2605
groby@chromium.org411034a2013-02-26 15:12:01 +00002606def CMDset_close(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002607 """Closes the issue."""
groby@chromium.org411034a2013-02-26 15:12:01 +00002608 _, args = parser.parse_args(args)
2609 if args:
2610 parser.error('Unrecognized args: %s' % ' '.join(args))
2611 cl = Changelist()
2612 # Ensure there actually is an issue to close.
2613 cl.GetDescription()
2614 cl.CloseIssue()
2615 return 0
2616
2617
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002618def CMDdiff(parser, args):
2619 """shows differences between local tree and last upload."""
2620 cl = Changelist()
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002621 issue = cl.GetIssue()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002622 branch = cl.GetBranch()
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002623 if not issue:
2624 DieWithError('No issue found for current branch (%s)' % branch)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002625 TMP_BRANCH = 'git-cl-diff'
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002626 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002627
2628 # Create a new branch based on the merge-base
2629 RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch])
2630 try:
2631 # Patch in the latest changes from rietveld.
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002632 rtn = PatchIssue(issue, False, False, None)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002633 if rtn != 0:
2634 return rtn
2635
2636 # Switch back to starting brand and diff against the temporary
2637 # branch containing the latest rietveld patch.
2638 subprocess2.check_call(['git', 'diff', TMP_BRANCH, branch])
2639 finally:
2640 RunGit(['checkout', '-q', branch])
2641 RunGit(['branch', '-D', TMP_BRANCH])
2642
2643 return 0
2644
2645
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002646def CMDowners(parser, args):
2647 """interactively find the owners for reviewing"""
2648 parser.add_option(
2649 '--no-color',
2650 action='store_true',
2651 help='Use this option to disable color output')
2652 options, args = parser.parse_args(args)
2653
2654 author = RunGit(['config', 'user.email']).strip() or None
2655
2656 cl = Changelist()
2657
2658 if args:
2659 if len(args) > 1:
2660 parser.error('Unknown args')
2661 base_branch = args[0]
2662 else:
2663 # Default to diffing against the common ancestor of the upstream branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002664 base_branch = cl.GetCommonAncestorWithUpstream()
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002665
2666 change = cl.GetChange(base_branch, None)
2667 return owners_finder.OwnersFinder(
2668 [f.LocalPath() for f in
2669 cl.GetChange(base_branch, None).AffectedFiles()],
2670 change.RepositoryRoot(), author,
2671 fopen=file, os_path=os.path, glob=glob.glob,
2672 disable_color=options.no_color).run()
2673
2674
enne@chromium.org555cfe42014-01-29 18:21:39 +00002675@subcommand.usage('[files or directories to diff]')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002676def CMDformat(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002677 """Runs clang-format on the diff."""
nick@chromium.org8ca1aa32014-02-25 23:57:03 +00002678 CLANG_EXTS = ['.cc', '.cpp', '.h', '.mm', '.proto']
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002679 parser.add_option('--full', action='store_true',
2680 help='Reformat the full content of all touched files')
2681 parser.add_option('--dry-run', action='store_true',
2682 help='Don\'t modify any file on disk.')
wittman@chromium.org04d5a222014-03-07 18:30:42 +00002683 parser.add_option('--diff', action='store_true',
2684 help='Print diff to stdout rather than modifying files.')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002685 opts, args = parser.parse_args(args)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002686
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00002687 # git diff generates paths against the root of the repository. Change
2688 # to that directory so clang-format can find files even within subdirs.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002689 rel_base_path = settings.GetRelativeRoot()
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00002690 if rel_base_path:
2691 os.chdir(rel_base_path)
2692
digit@chromium.org29e47272013-05-17 17:01:46 +00002693 # Generate diff for the current branch's changes.
enne@chromium.org90d30c62013-05-29 16:09:49 +00002694 diff_cmd = ['diff', '--no-ext-diff', '--no-prefix']
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002695 if opts.full:
digit@chromium.org29e47272013-05-17 17:01:46 +00002696 # Only list the names of modified files.
2697 diff_cmd.append('--name-only')
2698 else:
2699 # Only generate context-less patches.
2700 diff_cmd.append('-U0')
2701
2702 # Grab the merge-base commit, i.e. the upstream commit of the current
2703 # branch when it was created or the last time it was rebased. This is
2704 # to cover the case where the user may have called "git fetch origin",
2705 # moving the origin branch to a newer commit, but hasn't rebased yet.
2706 upstream_commit = None
2707 cl = Changelist()
2708 upstream_branch = cl.GetUpstreamBranch()
2709 if upstream_branch:
2710 upstream_commit = RunGit(['merge-base', 'HEAD', upstream_branch])
2711 upstream_commit = upstream_commit.strip()
2712
2713 if not upstream_commit:
2714 DieWithError('Could not find base commit for this branch. '
2715 'Are you in detached state?')
2716
2717 diff_cmd.append(upstream_commit)
2718
2719 # Handle source file filtering.
2720 diff_cmd.append('--')
enne@chromium.org555cfe42014-01-29 18:21:39 +00002721 if args:
2722 for arg in args:
2723 if os.path.isdir(arg):
2724 diff_cmd += [os.path.join(arg, '*' + ext) for ext in CLANG_EXTS]
2725 elif os.path.isfile(arg):
2726 diff_cmd.append(arg)
2727 else:
2728 DieWithError('Argument "%s" is not a file or a directory' % arg)
2729 else:
2730 diff_cmd += ['*' + ext for ext in CLANG_EXTS]
digit@chromium.org29e47272013-05-17 17:01:46 +00002731 diff_output = RunGit(diff_cmd)
2732
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00002733 top_dir = os.path.normpath(
2734 RunGit(["rev-parse", "--show-toplevel"]).rstrip('\n'))
2735
2736 # Locate the clang-format binary in the checkout
2737 try:
2738 clang_format_tool = clang_format.FindClangFormatToolInChromiumTree()
2739 except clang_format.NotFoundError, e:
2740 DieWithError(e)
mdempsky@google.comc3b3dc02013-08-05 23:09:49 +00002741
digit@chromium.org29e47272013-05-17 17:01:46 +00002742 if opts.full:
2743 # diff_output is a list of files to send to clang-format.
2744 files = diff_output.splitlines()
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002745 if not files:
2746 print "Nothing to format."
2747 return 0
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002748 cmd = [clang_format_tool]
wittman@chromium.org04d5a222014-03-07 18:30:42 +00002749 if not opts.dry_run and not opts.diff:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002750 cmd.append('-i')
2751 stdout = RunCommand(cmd + files, cwd=top_dir)
wittman@chromium.org04d5a222014-03-07 18:30:42 +00002752 if opts.diff:
2753 sys.stdout.write(stdout)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002754 else:
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00002755 env = os.environ.copy()
2756 env['PATH'] = os.path.dirname(clang_format_tool)
digit@chromium.org29e47272013-05-17 17:01:46 +00002757 # diff_output is a patch to send to clang-format-diff.py
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00002758 try:
2759 script = clang_format.FindClangFormatScriptInChromiumTree(
2760 'clang-format-diff.py')
2761 except clang_format.NotFoundError, e:
2762 DieWithError(e)
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00002763
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002764 cmd = [sys.executable, script, '-p0']
wittman@chromium.org04d5a222014-03-07 18:30:42 +00002765 if not opts.dry_run and not opts.diff:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002766 cmd.append('-i')
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00002767
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002768 stdout = RunCommand(cmd, stdin=diff_output, cwd=top_dir, env=env)
wittman@chromium.org04d5a222014-03-07 18:30:42 +00002769 if opts.diff:
2770 sys.stdout.write(stdout)
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002771 if opts.dry_run and len(stdout) > 0:
2772 return 2
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002773
2774 return 0
2775
2776
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002777class OptionParser(optparse.OptionParser):
2778 """Creates the option parse and add --verbose support."""
2779 def __init__(self, *args, **kwargs):
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002780 optparse.OptionParser.__init__(
2781 self, *args, prog='git cl', version=__version__, **kwargs)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002782 self.add_option(
2783 '-v', '--verbose', action='count', default=0,
2784 help='Use 2 times for more debugging info')
2785
2786 def parse_args(self, args=None, values=None):
2787 options, args = optparse.OptionParser.parse_args(self, args, values)
2788 levels = [logging.WARNING, logging.INFO, logging.DEBUG]
2789 logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)])
2790 return options, args
2791
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002792
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002793def main(argv):
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002794 if sys.hexversion < 0x02060000:
2795 print >> sys.stderr, (
2796 '\nYour python version %s is unsupported, please upgrade.\n' %
2797 sys.version.split(' ', 1)[0])
2798 return 2
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00002799
maruel@chromium.orgddd59412011-11-30 14:20:38 +00002800 # Reload settings.
2801 global settings
2802 settings = Settings()
2803
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002804 colorize_CMDstatus_doc()
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002805 dispatcher = subcommand.CommandDispatcher(__name__)
2806 try:
2807 return dispatcher.execute(OptionParser(), argv)
2808 except urllib2.HTTPError, e:
2809 if e.code != 500:
2810 raise
2811 DieWithError(
2812 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
2813 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002814
2815
2816if __name__ == '__main__':
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00002817 # These affect sys.stdout so do it outside of main() to simplify mocks in
2818 # unit testing.
maruel@chromium.org6f09cd92011-04-01 16:38:12 +00002819 fix_encoding.fix_encoding()
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00002820 colorama.init()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002821 sys.exit(main(sys.argv[1:]))