blob: b13dc4d0cf7c80b0587bed1cad2e6bcd60c5f881 [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
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002055 killed = False
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002056 if pushed_to_pending:
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002057 try:
2058 revision = WaitForRealCommit(remote, revision, base_branch, branch)
2059 # We set pushed_to_pending to False, since it made it all the way to the
2060 # real ref.
2061 pushed_to_pending = False
2062 except KeyboardInterrupt:
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002063 killed = True
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002064
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002065 if cl.GetIssue():
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002066 to_pending = ' to pending queue' if pushed_to_pending else ''
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002067 viewvc_url = settings.GetViewVCUrl()
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002068 if not to_pending:
2069 if viewvc_url and revision:
2070 change_desc.append_footer(
2071 'Committed: %s%s' % (viewvc_url, revision))
2072 elif revision:
2073 change_desc.append_footer('Committed: %s' % (revision,))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002074 print ('Closing issue '
2075 '(you may be prompted for your codereview password)...')
maruel@chromium.org78936cb2013-04-11 00:17:52 +00002076 cl.UpdateDescription(change_desc.description)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002077 cl.CloseIssue()
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002078 props = cl.GetIssueProperties()
sadrul@chromium.org34b5d822013-02-18 01:39:24 +00002079 patch_num = len(props['patchsets'])
rmistry@google.com52d224a2014-08-27 14:44:41 +00002080 comment = "Committed patchset #%d (id:%d)%s manually as %s" % (
iannucci@chromium.org34504a12014-08-29 23:51:37 +00002081 patch_num, props['patchsets'][-1], to_pending, revision[:7])
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002082 if options.bypass_hooks:
2083 comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.'
2084 else:
2085 comment += ' (presubmit successful).'
iannucci@chromium.orgb85a3162013-01-26 01:11:13 +00002086 cl.RpcServer().add_comment(cl.GetIssue(), comment)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002087 cl.SetIssue(None)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002088
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002089 if pushed_to_pending:
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002090 _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
2091 print 'The commit is in the pending queue (%s).' % pending_ref
2092 print (
thakis@chromium.org5f32a962014-09-05 21:33:23 +00002093 'It will show up on %s in ~1 min, once it gets a Cr-Commit-Position '
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002094 'footer.' % branch)
2095
iannucci@chromium.org6c217b12014-08-29 22:10:59 +00002096 hook = POSTUPSTREAM_HOOK_PATTERN % cmd
2097 if os.path.isfile(hook):
2098 RunCommand([hook, merge_base], error_ok=True)
maruel@chromium.org0ba7f962011-01-11 22:13:58 +00002099
iannucci@chromium.orgbbe9cc52014-09-05 18:25:51 +00002100 return 1 if killed else 0
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002101
2102
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002103def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
2104 print
2105 print 'Waiting for commit to be landed on %s...' % real_ref
2106 print '(If you are impatient, you may Ctrl-C once without harm)'
2107 target_tree = RunGit(['rev-parse', '%s:' % pushed_commit]).strip()
2108 current_rev = RunGit(['rev-parse', local_base_ref]).strip()
2109
2110 loop = 0
2111 while True:
2112 sys.stdout.write('fetching (%d)... \r' % loop)
2113 sys.stdout.flush()
2114 loop += 1
2115
2116 RunGit(['retry', 'fetch', remote, real_ref], stderr=subprocess2.VOID)
2117 to_rev = RunGit(['rev-parse', 'FETCH_HEAD']).strip()
2118 commits = RunGit(['rev-list', '%s..%s' % (current_rev, to_rev)])
2119 for commit in commits.splitlines():
2120 if RunGit(['rev-parse', '%s:' % commit]).strip() == target_tree:
2121 print 'Found commit on %s' % real_ref
2122 return commit
2123
2124 current_rev = to_rev
2125
2126
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002127def PushToGitPending(remote, pending_ref, upstream_ref):
2128 """Fetches pending_ref, cherry-picks current HEAD on top of it, pushes.
2129
2130 Returns:
2131 (retcode of last operation, output log of last operation).
2132 """
2133 assert pending_ref.startswith('refs/'), pending_ref
2134 local_pending_ref = 'refs/git-cl/' + pending_ref[len('refs/'):]
2135 cherry = RunGit(['rev-parse', 'HEAD']).strip()
2136 code = 0
2137 out = ''
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002138 max_attempts = 3
2139 attempts_left = max_attempts
2140 while attempts_left:
2141 if attempts_left != max_attempts:
2142 print 'Retrying, %d attempts left...' % (attempts_left - 1,)
2143 attempts_left -= 1
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002144
2145 # Fetch. Retry fetch errors.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002146 print 'Fetching pending ref %s...' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002147 code, out = RunGitWithCode(
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002148 ['retry', 'fetch', remote, '+%s:%s' % (pending_ref, local_pending_ref)])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002149 if code:
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002150 print 'Fetch failed with exit code %d.' % code
2151 if out.strip():
2152 print out.strip()
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002153 continue
2154
2155 # Try to cherry pick. Abort on merge conflicts.
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002156 print 'Cherry-picking commit on top of pending ref...'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002157 RunGitWithCode(['checkout', local_pending_ref], suppress_stderr=True)
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002158 code, out = RunGitWithCode(['cherry-pick', cherry])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002159 if code:
2160 print (
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002161 'Your patch doesn\'t apply cleanly to ref \'%s\', '
2162 'the following files have merge conflicts:' % pending_ref)
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002163 print RunGit(['diff', '--name-status', '--diff-filter=U']).strip()
2164 print 'Please rebase your patch and try again.'
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002165 RunGitWithCode(['cherry-pick', '--abort'])
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002166 return code, out
2167
2168 # Applied cleanly, try to push now. Retry on error (flake or non-ff push).
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002169 print 'Pushing commit to %s... It can take a while.' % pending_ref
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002170 code, out = RunGitWithCode(
2171 ['retry', 'push', '--porcelain', remote, 'HEAD:%s' % pending_ref])
2172 if code == 0:
2173 # Success.
iannucci@chromium.orge6896b52014-08-29 01:38:03 +00002174 print 'Commit pushed to pending ref successfully!'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002175 return code, out
2176
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002177 print 'Push failed with exit code %d.' % code
2178 if out.strip():
2179 print out.strip()
2180 if IsFatalPushFailure(out):
2181 print (
2182 'Fatal push error. Make sure your .netrc credentials and git '
2183 'user.email are correct and you have push access to the repo.')
2184 return code, out
2185
2186 print 'All attempts to push to pending ref failed.'
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002187 return code, out
2188
2189
vadimsh@chromium.org749fbd92014-08-26 21:57:53 +00002190def IsFatalPushFailure(push_stdout):
2191 """True if retrying push won't help."""
2192 return '(prohibited by Gerrit)' in push_stdout
2193
2194
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002195@subcommand.usage('[upstream branch to apply against]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002196def CMDdcommit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002197 """Commits the current changelist via git-svn."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002198 if not settings.GetIsGitSvn():
thakis@chromium.orgcde3bb62011-01-20 01:16:14 +00002199 message = """This doesn't appear to be an SVN repository.
2200If your project has a git mirror with an upstream SVN master, you probably need
2201to run 'git svn init', see your project's git mirror documentation.
2202If your project has a true writeable upstream repository, you probably want
mark@chromium.org671c7a32014-07-31 17:09:36 +00002203to run 'git cl land' instead.
thakis@chromium.orgcde3bb62011-01-20 01:16:14 +00002204Choose wisely, if you get this wrong, your commit might appear to succeed but
2205will instead be silently ignored."""
2206 print(message)
maruel@chromium.org90541732011-04-01 17:54:18 +00002207 ask_for_data('[Press enter to dcommit or ctrl-C to quit]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002208 return SendUpstream(parser, args, 'dcommit')
2209
2210
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002211@subcommand.usage('[upstream branch to apply against]')
pgervais@chromium.orgcee6dc42014-05-07 17:04:03 +00002212def CMDland(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002213 """Commits the current changelist via git."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002214 if settings.GetIsGitSvn():
2215 print('This appears to be an SVN repository.')
2216 print('Are you sure you didn\'t mean \'git cl dcommit\'?')
maruel@chromium.org90541732011-04-01 17:54:18 +00002217 ask_for_data('[Press enter to push or ctrl-C to quit]')
vadimsh@chromium.org566a02a2014-08-22 01:34:13 +00002218 return SendUpstream(parser, args, 'land')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002219
2220
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002221@subcommand.usage('<patch url or issue id>')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002222def CMDpatch(parser, args):
marq@chromium.orge5e59002013-10-02 23:21:25 +00002223 """Patches in a code review."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002224 parser.add_option('-b', dest='newbranch',
2225 help='create a new branch off trunk for the patch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002226 parser.add_option('-f', '--force', action='store_true',
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002227 help='with -b, clobber any existing branch')
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002228 parser.add_option('-d', '--directory', action='store', metavar='DIR',
2229 help='Change to the directory DIR immediately, '
2230 'before doing anything else.')
2231 parser.add_option('--reject', action='store_true',
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00002232 help='failed patches spew .rej files rather than '
2233 'attempting a 3-way merge')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002234 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit',
2235 help="don't commit after patch applies")
2236 (options, args) = parser.parse_args(args)
2237 if len(args) != 1:
2238 parser.print_help()
2239 return 1
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002240 issue_arg = args[0]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002241
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002242 # TODO(maruel): Use apply_issue.py
ukai@chromium.orge8077812012-02-03 03:41:46 +00002243 # TODO(ukai): use gerrit-cherry-pick for gerrit repository?
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002244
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002245 if options.newbranch:
2246 if options.force:
2247 RunGit(['branch', '-D', options.newbranch],
2248 stderr=subprocess2.PIPE, error_ok=True)
2249 RunGit(['checkout', '-b', options.newbranch,
2250 Changelist().GetUpstreamBranch()])
2251
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002252 return PatchIssue(issue_arg, options.reject, options.nocommit,
2253 options.directory)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002254
2255
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002256def PatchIssue(issue_arg, reject, nocommit, directory):
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002257 if type(issue_arg) is int or issue_arg.isdigit():
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002258 # Input is an issue id. Figure out the URL.
maruel@chromium.org52424302012-08-29 15:14:30 +00002259 issue = int(issue_arg)
jochen@chromium.orga26e0472013-07-24 10:25:01 +00002260 cl = Changelist(issue=issue)
maruel@chromium.org1033efd2013-07-23 23:25:09 +00002261 patchset = cl.GetMostRecentPatchset()
binji@chromium.org0281f522012-09-14 13:37:59 +00002262 patch_data = cl.GetPatchSetDiff(issue, patchset)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002263 else:
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00002264 # Assume it's a URL to the patch. Default to https.
2265 issue_url = gclient_utils.UpgradeToHttps(issue_arg)
binji@chromium.org0281f522012-09-14 13:37:59 +00002266 match = re.match(r'.*?/issue(\d+)_(\d+).diff', issue_url)
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002267 if not match:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002268 DieWithError('Must pass an issue ID or full URL for '
2269 '\'Download raw patch set\'')
maruel@chromium.org52424302012-08-29 15:14:30 +00002270 issue = int(match.group(1))
binji@chromium.org0281f522012-09-14 13:37:59 +00002271 patchset = int(match.group(2))
maruel@chromium.orge77ebbf2011-03-29 20:35:38 +00002272 patch_data = urllib2.urlopen(issue_arg).read()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002273
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002274 # Switch up to the top-level directory, if necessary, in preparation for
2275 # applying the patch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002276 top = settings.GetRelativeRoot()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002277 if top:
2278 os.chdir(top)
2279
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002280 # Git patches have a/ at the beginning of source paths. We strip that out
2281 # with a sed script rather than the -p flag to patch so we can feed either
2282 # Git or svn-style patches into the same apply command.
2283 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002284 try:
2285 patch_data = subprocess2.check_output(
2286 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
2287 except subprocess2.CalledProcessError:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002288 DieWithError('Git patch mungling failed.')
2289 logging.info(patch_data)
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002290
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002291 # We use "git apply" to apply the patch instead of "patch" so that we can
2292 # pick up file adds.
2293 # The --index flag means: also insert into the index (so we catch adds).
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002294 cmd = ['git', 'apply', '--index', '-p0']
qsr@chromium.org1ef44af2013-10-16 16:24:32 +00002295 if directory:
2296 cmd.extend(('--directory', directory))
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002297 if reject:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002298 cmd.append('--reject')
tapted@chromium.org6a0b07c2013-07-10 01:29:19 +00002299 elif IsGitVersionAtLeast('1.7.12'):
2300 cmd.append('--3way')
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002301 try:
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002302 subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002303 stdin=patch_data, stdout=subprocess2.VOID)
maruel@chromium.org32f9f5e2011-09-14 13:41:47 +00002304 except subprocess2.CalledProcessError:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002305 DieWithError('Failed to apply the patch')
2306
2307 # If we had an issue, commit the current state and register the issue.
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002308 if not nocommit:
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002309 RunGit(['commit', '-m', 'patch from issue %s' % issue])
2310 cl = Changelist()
2311 cl.SetIssue(issue)
binji@chromium.org0281f522012-09-14 13:37:59 +00002312 cl.SetPatchset(patchset)
pdr@chromium.org98ca6622013-04-09 20:58:40 +00002313 print "Committed patch locally."
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002314 else:
2315 print "Patch applied to index."
2316 return 0
2317
2318
2319def CMDrebase(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002320 """Rebases current branch on top of svn repo."""
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002321 # Provide a wrapper for git svn rebase to help avoid accidental
2322 # git svn dcommit.
2323 # It's the only command that doesn't use parser at all since we just defer
2324 # execution to git-svn.
bratell@opera.com82b91cd2013-07-09 06:33:41 +00002325
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002326 return RunGitWithCode(['svn', 'rebase'] + args)[1]
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002327
2328
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002329def GetTreeStatus(url=None):
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002330 """Fetches the tree status and returns either 'open', 'closed',
2331 'unknown' or 'unset'."""
jochen@chromium.org3ec0d542014-01-14 20:00:03 +00002332 url = url or settings.GetTreeStatusUrl(error_ok=True)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002333 if url:
2334 status = urllib2.urlopen(url).read().lower()
2335 if status.find('closed') != -1 or status == '0':
2336 return 'closed'
2337 elif status.find('open') != -1 or status == '1':
2338 return 'open'
2339 return 'unknown'
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002340 return 'unset'
2341
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002342
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002343def GetTreeStatusReason():
2344 """Fetches the tree status from a json url and returns the message
2345 with the reason for the tree to be opened or closed."""
msb@chromium.orgbf1a7ba2011-02-01 16:21:46 +00002346 url = settings.GetTreeStatusUrl()
2347 json_url = urlparse.urljoin(url, '/current?format=json')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002348 connection = urllib2.urlopen(json_url)
2349 status = json.loads(connection.read())
2350 connection.close()
2351 return status['message']
2352
dpranke@chromium.org970c5222011-03-12 00:32:24 +00002353
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002354def GetBuilderMaster(bot_list):
2355 """For a given builder, fetch the master from AE if available."""
2356 map_url = 'https://builders-map.appspot.com/'
2357 try:
2358 master_map = json.load(urllib2.urlopen(map_url))
2359 except urllib2.URLError as e:
2360 return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' %
2361 (map_url, e))
2362 except ValueError as e:
2363 return None, ('Invalid json string from %s. Error: %s.' % (map_url, e))
2364 if not master_map:
2365 return None, 'Failed to build master map.'
2366
2367 result_master = ''
2368 for bot in bot_list:
2369 builder = bot.split(':', 1)[0]
2370 master_list = master_map.get(builder, [])
2371 if not master_list:
2372 return None, ('No matching master for builder %s.' % builder)
2373 elif len(master_list) > 1:
2374 return None, ('The builder name %s exists in multiple masters %s.' %
2375 (builder, master_list))
2376 else:
2377 cur_master = master_list[0]
2378 if not result_master:
2379 result_master = cur_master
2380 elif result_master != cur_master:
2381 return None, 'The builders do not belong to the same master.'
2382 return result_master, None
2383
2384
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002385def CMDtree(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002386 """Shows the status of the tree."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002387 _, args = parser.parse_args(args)
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002388 status = GetTreeStatus()
2389 if 'unset' == status:
2390 print 'You must configure your tree status URL by running "git cl config".'
2391 return 2
2392
2393 print "The tree is %s" % status
2394 print
2395 print GetTreeStatusReason()
2396 if status != 'open':
2397 return 1
2398 return 0
2399
2400
maruel@chromium.org15192402012-09-06 12:38:29 +00002401def CMDtry(parser, args):
2402 """Triggers a try job through Rietveld."""
2403 group = optparse.OptionGroup(parser, "Try job options")
2404 group.add_option(
2405 "-b", "--bot", action="append",
2406 help=("IMPORTANT: specify ONE builder per --bot flag. Use it multiple "
2407 "times to specify multiple builders. ex: "
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002408 "'-b win_rel:ui_tests,webkit_unit_tests -b win_layout'. See "
maruel@chromium.org15192402012-09-06 12:38:29 +00002409 "the try server waterfall for the builders name and the tests "
2410 "available. Can also be used to specify gtest_filter, e.g. "
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002411 "-b win_rel:base_unittests:ValuesTest.*Value"))
maruel@chromium.org15192402012-09-06 12:38:29 +00002412 group.add_option(
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002413 "-m", "--master", default='',
iannucci@chromium.org9e849272014-04-04 00:31:55 +00002414 help=("Specify a try master where to run the tries."))
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002415 group.add_option(
maruel@chromium.org15192402012-09-06 12:38:29 +00002416 "-r", "--revision",
2417 help="Revision to use for the try job; default: the "
2418 "revision will be determined by the try server; see "
2419 "its waterfall for more info")
2420 group.add_option(
2421 "-c", "--clobber", action="store_true", default=False,
2422 help="Force a clobber before building; e.g. don't do an "
2423 "incremental build")
2424 group.add_option(
2425 "--project",
2426 help="Override which project to use. Projects are defined "
2427 "server-side to define what default bot set to use")
2428 group.add_option(
2429 "-t", "--testfilter", action="append", default=[],
2430 help=("Apply a testfilter to all the selected builders. Unless the "
2431 "builders configurations are similar, use multiple "
2432 "--bot <builder>:<test> arguments."))
2433 group.add_option(
2434 "-n", "--name", help="Try job name; default to current branch name")
2435 parser.add_option_group(group)
2436 options, args = parser.parse_args(args)
2437
2438 if args:
2439 parser.error('Unknown arguments: %s' % args)
2440
2441 cl = Changelist()
2442 if not cl.GetIssue():
2443 parser.error('Need to upload first')
2444
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002445 props = cl.GetIssueProperties()
agable@chromium.org787e3062014-08-20 16:31:19 +00002446 if props.get('closed'):
2447 parser.error('Cannot send tryjobs for a closed CL')
2448
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002449 if props.get('private'):
2450 parser.error('Cannot use trybots with private issue')
2451
maruel@chromium.org15192402012-09-06 12:38:29 +00002452 if not options.name:
2453 options.name = cl.GetBranch()
2454
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00002455 if options.bot and not options.master:
sheyang@chromium.org2b34d552014-08-14 22:18:42 +00002456 options.master, err_msg = GetBuilderMaster(options.bot)
2457 if err_msg:
2458 parser.error('Tryserver master cannot be found because: %s\n'
2459 'Please manually specify the tryserver master'
2460 ', e.g. "-m tryserver.chromium.linux".' % err_msg)
phajdan.jr@chromium.org8da7f272014-03-14 01:28:39 +00002461
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002462 def GetMasterMap():
2463 # Process --bot and --testfilter.
2464 if not options.bot:
2465 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
maruel@chromium.org15192402012-09-06 12:38:29 +00002466
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002467 # Get try masters from PRESUBMIT.py files.
2468 masters = presubmit_support.DoGetTryMasters(
2469 change,
2470 change.LocalPaths(),
2471 settings.GetRoot(),
2472 None,
2473 None,
2474 options.verbose,
2475 sys.stdout)
2476 if masters:
2477 return masters
stip@chromium.org43064fd2013-12-18 20:07:44 +00002478
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002479 # Fall back to deprecated method: get try slaves from PRESUBMIT.py files.
2480 options.bot = presubmit_support.DoGetTrySlaves(
2481 change,
2482 change.LocalPaths(),
2483 settings.GetRoot(),
2484 None,
2485 None,
2486 options.verbose,
2487 sys.stdout)
2488 if not options.bot:
2489 parser.error('No default try builder to try, use --bot')
maruel@chromium.org15192402012-09-06 12:38:29 +00002490
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002491 builders_and_tests = {}
2492 # TODO(machenbach): The old style command-line options don't support
2493 # multiple try masters yet.
2494 old_style = filter(lambda x: isinstance(x, basestring), options.bot)
2495 new_style = filter(lambda x: isinstance(x, tuple), options.bot)
2496
2497 for bot in old_style:
2498 if ':' in bot:
2499 builder, tests = bot.split(':', 1)
2500 builders_and_tests.setdefault(builder, []).extend(tests.split(','))
2501 elif ',' in bot:
2502 parser.error('Specify one bot per --bot flag')
2503 else:
2504 builders_and_tests.setdefault(bot, []).append('defaulttests')
2505
2506 for bot, tests in new_style:
2507 builders_and_tests.setdefault(bot, []).extend(tests)
2508
2509 # Return a master map with one master to be backwards compatible. The
2510 # master name defaults to an empty string, which will cause the master
2511 # not to be set on rietveld (deprecated).
2512 return {options.master: builders_and_tests}
2513
2514 masters = GetMasterMap()
stip@chromium.org43064fd2013-12-18 20:07:44 +00002515
maruel@chromium.org15192402012-09-06 12:38:29 +00002516 if options.testfilter:
2517 forced_tests = sum((t.split(',') for t in options.testfilter), [])
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002518 masters = dict((master, dict(
2519 (b, forced_tests) for b, t in slaves.iteritems()
2520 if t != ['compile'])) for master, slaves in masters.iteritems())
maruel@chromium.org15192402012-09-06 12:38:29 +00002521
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002522 for builders in masters.itervalues():
2523 if any('triggered' in b for b in builders):
2524 print >> sys.stderr, (
2525 'ERROR You are trying to send a job to a triggered bot. This type of'
2526 ' bot requires an\ninitial job from a parent (usually a builder). '
2527 'Instead send your job to the parent.\n'
2528 'Bot list: %s' % builders)
2529 return 1
ilevy@chromium.orgf3b21232012-09-24 20:48:55 +00002530
ilevy@chromium.org36e420b2013-08-06 23:21:12 +00002531 patchset = cl.GetMostRecentPatchset()
2532 if patchset and patchset != cl.GetPatchset():
2533 print(
2534 '\nWARNING Mismatch between local config and server. Did a previous '
2535 'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
2536 'Continuing using\npatchset %s.\n' % patchset)
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002537 try:
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002538 cl.RpcServer().trigger_distributed_try_jobs(
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002539 cl.GetIssue(), patchset, options.name, options.clobber,
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002540 options.revision, masters)
fischman@chromium.orgd246c972013-12-21 22:47:38 +00002541 except urllib2.HTTPError, e:
2542 if e.code == 404:
2543 print('404 from rietveld; '
2544 'did you mean to use "git try" instead of "git cl try"?')
2545 return 1
maruel@chromium.org072d94b2012-09-20 19:20:08 +00002546 print('Tried jobs on:')
machenbach@chromium.org58a69cb2014-03-01 02:08:29 +00002547
2548 for (master, builders) in masters.iteritems():
2549 if master:
2550 print 'Master: %s' % master
2551 length = max(len(builder) for builder in builders)
2552 for builder in sorted(builders):
2553 print ' %*s: %s' % (length, builder, ','.join(builders[builder]))
maruel@chromium.org15192402012-09-06 12:38:29 +00002554 return 0
2555
2556
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002557@subcommand.usage('[new upstream branch]')
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002558def CMDupstream(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002559 """Prints or sets the name of the upstream branch, if any."""
dpranke@chromium.org97ae58e2011-03-18 00:29:20 +00002560 _, args = parser.parse_args(args)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002561 if len(args) > 1:
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002562 parser.error('Unrecognized args: %s' % ' '.join(args))
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002563
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002564 cl = Changelist()
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002565 if args:
2566 # One arg means set upstream branch.
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00002567 branch = cl.GetBranch()
2568 RunGit(['branch', '--set-upstream', branch, args[0]])
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002569 cl = Changelist()
2570 print "Upstream branch set to " + cl.GetUpstreamBranch()
bauerb@chromium.orgc9cf90a2014-04-28 20:32:31 +00002571
2572 # Clear configured merge-base, if there is one.
2573 git_common.remove_merge_base(branch)
brettw@chromium.orgac0ba332012-08-09 23:42:53 +00002574 else:
2575 print cl.GetUpstreamBranch()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002576 return 0
2577
2578
thestig@chromium.org00858c82013-12-02 23:08:03 +00002579def CMDweb(parser, args):
2580 """Opens the current CL in the web browser."""
2581 _, args = parser.parse_args(args)
2582 if args:
2583 parser.error('Unrecognized args: %s' % ' '.join(args))
2584
2585 issue_url = Changelist().GetIssueURL()
2586 if not issue_url:
2587 print >> sys.stderr, 'ERROR No issue to open'
2588 return 1
2589
2590 webbrowser.open(issue_url)
2591 return 0
2592
2593
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002594def CMDset_commit(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002595 """Sets the commit bit to trigger the Commit Queue."""
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002596 _, args = parser.parse_args(args)
2597 if args:
2598 parser.error('Unrecognized args: %s' % ' '.join(args))
2599 cl = Changelist()
jrobbins@chromium.org16f10f72014-06-24 22:14:36 +00002600 props = cl.GetIssueProperties()
2601 if props.get('private'):
2602 parser.error('Cannot set commit on private issue')
maruel@chromium.org27bb3872011-05-30 20:33:19 +00002603 cl.SetFlag('commit', '1')
2604 return 0
2605
2606
groby@chromium.org411034a2013-02-26 15:12:01 +00002607def CMDset_close(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002608 """Closes the issue."""
groby@chromium.org411034a2013-02-26 15:12:01 +00002609 _, args = parser.parse_args(args)
2610 if args:
2611 parser.error('Unrecognized args: %s' % ' '.join(args))
2612 cl = Changelist()
2613 # Ensure there actually is an issue to close.
2614 cl.GetDescription()
2615 cl.CloseIssue()
2616 return 0
2617
2618
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002619def CMDdiff(parser, args):
2620 """shows differences between local tree and last upload."""
2621 cl = Changelist()
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002622 issue = cl.GetIssue()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002623 branch = cl.GetBranch()
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002624 if not issue:
2625 DieWithError('No issue found for current branch (%s)' % branch)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002626 TMP_BRANCH = 'git-cl-diff'
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002627 base_branch = cl.GetCommonAncestorWithUpstream()
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002628
2629 # Create a new branch based on the merge-base
2630 RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch])
2631 try:
2632 # Patch in the latest changes from rietveld.
sbc@chromium.org78dc9842013-11-25 18:43:44 +00002633 rtn = PatchIssue(issue, False, False, None)
sbc@chromium.org87b9bf02013-09-26 20:35:15 +00002634 if rtn != 0:
2635 return rtn
2636
2637 # Switch back to starting brand and diff against the temporary
2638 # branch containing the latest rietveld patch.
2639 subprocess2.check_call(['git', 'diff', TMP_BRANCH, branch])
2640 finally:
2641 RunGit(['checkout', '-q', branch])
2642 RunGit(['branch', '-D', TMP_BRANCH])
2643
2644 return 0
2645
2646
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002647def CMDowners(parser, args):
2648 """interactively find the owners for reviewing"""
2649 parser.add_option(
2650 '--no-color',
2651 action='store_true',
2652 help='Use this option to disable color output')
2653 options, args = parser.parse_args(args)
2654
2655 author = RunGit(['config', 'user.email']).strip() or None
2656
2657 cl = Changelist()
2658
2659 if args:
2660 if len(args) > 1:
2661 parser.error('Unknown args')
2662 base_branch = args[0]
2663 else:
2664 # Default to diffing against the common ancestor of the upstream branch.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002665 base_branch = cl.GetCommonAncestorWithUpstream()
ikarienator@chromium.orgfaf3fdf2013-09-20 02:11:48 +00002666
2667 change = cl.GetChange(base_branch, None)
2668 return owners_finder.OwnersFinder(
2669 [f.LocalPath() for f in
2670 cl.GetChange(base_branch, None).AffectedFiles()],
2671 change.RepositoryRoot(), author,
2672 fopen=file, os_path=os.path, glob=glob.glob,
2673 disable_color=options.no_color).run()
2674
2675
enne@chromium.org555cfe42014-01-29 18:21:39 +00002676@subcommand.usage('[files or directories to diff]')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002677def CMDformat(parser, args):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002678 """Runs clang-format on the diff."""
nick@chromium.org8ca1aa32014-02-25 23:57:03 +00002679 CLANG_EXTS = ['.cc', '.cpp', '.h', '.mm', '.proto']
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002680 parser.add_option('--full', action='store_true',
2681 help='Reformat the full content of all touched files')
2682 parser.add_option('--dry-run', action='store_true',
2683 help='Don\'t modify any file on disk.')
wittman@chromium.org04d5a222014-03-07 18:30:42 +00002684 parser.add_option('--diff', action='store_true',
2685 help='Print diff to stdout rather than modifying files.')
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002686 opts, args = parser.parse_args(args)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002687
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00002688 # git diff generates paths against the root of the repository. Change
2689 # to that directory so clang-format can find files even within subdirs.
thestig@chromium.org8b0553c2014-02-11 00:33:37 +00002690 rel_base_path = settings.GetRelativeRoot()
enne@chromium.orgff7a1fb2013-12-10 19:21:41 +00002691 if rel_base_path:
2692 os.chdir(rel_base_path)
2693
digit@chromium.org29e47272013-05-17 17:01:46 +00002694 # Generate diff for the current branch's changes.
enne@chromium.org90d30c62013-05-29 16:09:49 +00002695 diff_cmd = ['diff', '--no-ext-diff', '--no-prefix']
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002696 if opts.full:
digit@chromium.org29e47272013-05-17 17:01:46 +00002697 # Only list the names of modified files.
2698 diff_cmd.append('--name-only')
2699 else:
2700 # Only generate context-less patches.
2701 diff_cmd.append('-U0')
2702
2703 # Grab the merge-base commit, i.e. the upstream commit of the current
2704 # branch when it was created or the last time it was rebased. This is
2705 # to cover the case where the user may have called "git fetch origin",
2706 # moving the origin branch to a newer commit, but hasn't rebased yet.
2707 upstream_commit = None
2708 cl = Changelist()
2709 upstream_branch = cl.GetUpstreamBranch()
2710 if upstream_branch:
2711 upstream_commit = RunGit(['merge-base', 'HEAD', upstream_branch])
2712 upstream_commit = upstream_commit.strip()
2713
2714 if not upstream_commit:
2715 DieWithError('Could not find base commit for this branch. '
2716 'Are you in detached state?')
2717
2718 diff_cmd.append(upstream_commit)
2719
2720 # Handle source file filtering.
2721 diff_cmd.append('--')
enne@chromium.org555cfe42014-01-29 18:21:39 +00002722 if args:
2723 for arg in args:
2724 if os.path.isdir(arg):
2725 diff_cmd += [os.path.join(arg, '*' + ext) for ext in CLANG_EXTS]
2726 elif os.path.isfile(arg):
2727 diff_cmd.append(arg)
2728 else:
2729 DieWithError('Argument "%s" is not a file or a directory' % arg)
2730 else:
2731 diff_cmd += ['*' + ext for ext in CLANG_EXTS]
digit@chromium.org29e47272013-05-17 17:01:46 +00002732 diff_output = RunGit(diff_cmd)
2733
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00002734 top_dir = os.path.normpath(
2735 RunGit(["rev-parse", "--show-toplevel"]).rstrip('\n'))
2736
2737 # Locate the clang-format binary in the checkout
2738 try:
2739 clang_format_tool = clang_format.FindClangFormatToolInChromiumTree()
2740 except clang_format.NotFoundError, e:
2741 DieWithError(e)
mdempsky@google.comc3b3dc02013-08-05 23:09:49 +00002742
digit@chromium.org29e47272013-05-17 17:01:46 +00002743 if opts.full:
2744 # diff_output is a list of files to send to clang-format.
2745 files = diff_output.splitlines()
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002746 if not files:
2747 print "Nothing to format."
2748 return 0
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002749 cmd = [clang_format_tool]
wittman@chromium.org04d5a222014-03-07 18:30:42 +00002750 if not opts.dry_run and not opts.diff:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002751 cmd.append('-i')
2752 stdout = RunCommand(cmd + files, cwd=top_dir)
wittman@chromium.org04d5a222014-03-07 18:30:42 +00002753 if opts.diff:
2754 sys.stdout.write(stdout)
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002755 else:
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00002756 env = os.environ.copy()
thakis@chromium.orgbd2a9b92014-09-05 18:54:22 +00002757 env['PATH'] = str(os.path.dirname(clang_format_tool))
digit@chromium.org29e47272013-05-17 17:01:46 +00002758 # diff_output is a patch to send to clang-format-diff.py
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +00002759 try:
2760 script = clang_format.FindClangFormatScriptInChromiumTree(
2761 'clang-format-diff.py')
2762 except clang_format.NotFoundError, e:
2763 DieWithError(e)
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00002764
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002765 cmd = [sys.executable, script, '-p0']
wittman@chromium.org04d5a222014-03-07 18:30:42 +00002766 if not opts.dry_run and not opts.diff:
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002767 cmd.append('-i')
digit@chromium.orgd6ddc1c2013-10-25 15:36:32 +00002768
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002769 stdout = RunCommand(cmd, stdin=diff_output, cwd=top_dir, env=env)
wittman@chromium.org04d5a222014-03-07 18:30:42 +00002770 if opts.diff:
2771 sys.stdout.write(stdout)
enne@chromium.org3b7e15c2014-01-21 17:44:47 +00002772 if opts.dry_run and len(stdout) > 0:
2773 return 2
agable@chromium.orgfab8f822013-05-06 17:43:09 +00002774
2775 return 0
2776
2777
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002778class OptionParser(optparse.OptionParser):
2779 """Creates the option parse and add --verbose support."""
2780 def __init__(self, *args, **kwargs):
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002781 optparse.OptionParser.__init__(
2782 self, *args, prog='git cl', version=__version__, **kwargs)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002783 self.add_option(
2784 '-v', '--verbose', action='count', default=0,
2785 help='Use 2 times for more debugging info')
2786
2787 def parse_args(self, args=None, values=None):
2788 options, args = optparse.OptionParser.parse_args(self, args, values)
2789 levels = [logging.WARNING, logging.INFO, logging.DEBUG]
2790 logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)])
2791 return options, args
2792
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002793
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002794def main(argv):
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002795 if sys.hexversion < 0x02060000:
2796 print >> sys.stderr, (
2797 '\nYour python version %s is unsupported, please upgrade.\n' %
2798 sys.version.split(' ', 1)[0])
2799 return 2
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00002800
maruel@chromium.orgddd59412011-11-30 14:20:38 +00002801 # Reload settings.
2802 global settings
2803 settings = Settings()
2804
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002805 colorize_CMDstatus_doc()
maruel@chromium.org0633fb42013-08-16 20:06:14 +00002806 dispatcher = subcommand.CommandDispatcher(__name__)
2807 try:
2808 return dispatcher.execute(OptionParser(), argv)
2809 except urllib2.HTTPError, e:
2810 if e.code != 500:
2811 raise
2812 DieWithError(
2813 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
2814 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002815
2816
2817if __name__ == '__main__':
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00002818 # These affect sys.stdout so do it outside of main() to simplify mocks in
2819 # unit testing.
maruel@chromium.org6f09cd92011-04-01 16:38:12 +00002820 fix_encoding.fix_encoding()
maruel@chromium.org2e23ce32013-05-07 12:42:28 +00002821 colorama.init()
chase@chromium.orgcc51cd02010-12-23 00:48:39 +00002822 sys.exit(main(sys.argv[1:]))