blob: 20762d1be0c5bdbb277170c046608585f459397a [file] [log] [blame]
[email protected]fb2b8eb2009-04-23 21:03:421#!/usr/bin/python
[email protected]8a8ea022010-11-01 13:27:322# Copyright (c) 2010 The Chromium Authors. All rights reserved.
[email protected]fb2b8eb2009-04-23 21:03:423# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
[email protected]ebbf9472009-11-10 20:26:305
[email protected]62fd6932010-05-27 13:13:236"""\
7Wrapper script around Rietveld's upload.py that simplifies working with groups
8of files.
9"""
[email protected]fb2b8eb2009-04-23 21:03:4210
11import getpass
[email protected]8e13a092010-11-02 19:06:0612import optparse
[email protected]fb2b8eb2009-04-23 21:03:4213import os
14import random
15import re
16import string
17import subprocess
18import sys
19import tempfile
[email protected]2f6a0d82010-05-12 00:03:3020import time
[email protected]ba551772010-02-03 18:21:4221from third_party import upload
[email protected]fb2b8eb2009-04-23 21:03:4222import urllib2
23
[email protected]fa3843e2010-11-01 13:34:3724try:
[email protected]d945f362011-03-11 22:52:1925 import simplejson as json # pylint: disable=F0401
[email protected]fa3843e2010-11-01 13:34:3726except ImportError:
27 try:
28 import json
29 # Some versions of python2.5 have an incomplete json module. Check to make
30 # sure loads exists.
31 # pylint: disable=W0104
32 json.loads
33 except (ImportError, AttributeError):
34 # Import the one included in depot_tools.
35 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party'))
[email protected]d945f362011-03-11 22:52:1936 import simplejson as json # pylint: disable=F0401
[email protected]fa3843e2010-11-01 13:34:3737
[email protected]cb2985f2010-11-03 14:08:3138import breakpad # pylint: disable=W0611
[email protected]ada4c652009-12-03 15:32:0139
[email protected]46a94102009-05-12 20:32:4340# gcl now depends on gclient.
[email protected]5aeb7dd2009-11-17 18:09:0141from scm import SVN
[email protected]30becc22011-03-17 01:21:2242
[email protected]5f3eee32009-09-17 00:34:3043import gclient_utils
[email protected]30becc22011-03-17 01:21:2244import owners
45import presubmit_support
[email protected]c1675e22009-04-27 20:30:4846
[email protected]62fd6932010-05-27 13:13:2347__version__ = '1.2'
[email protected]c1675e22009-04-27 20:30:4848
49
[email protected]fb2b8eb2009-04-23 21:03:4250CODEREVIEW_SETTINGS = {
[email protected]b8260242010-08-19 17:03:1651 # To make gcl send reviews to a server, check in a file named
[email protected]172b6e72010-01-26 00:35:0352 # "codereview.settings" (see |CODEREVIEW_SETTINGS_FILE| below) to your
53 # project's base directory and add the following line to codereview.settings:
54 # CODE_REVIEW_SERVER: codereview.yourserver.org
[email protected]fb2b8eb2009-04-23 21:03:4255}
56
[email protected]fb2b8eb2009-04-23 21:03:4257# globals that store the root of the current repository and the directory where
58# we store information about changelists.
[email protected]98fc2b92009-05-21 14:11:5159REPOSITORY_ROOT = ""
[email protected]fb2b8eb2009-04-23 21:03:4260
61# Filename where we store repository specific information for gcl.
62CODEREVIEW_SETTINGS_FILE = "codereview.settings"
[email protected]b8260242010-08-19 17:03:1663CODEREVIEW_SETTINGS_FILE_NOT_FOUND = (
64 'No %s file found. Please add one.' % CODEREVIEW_SETTINGS_FILE)
[email protected]fb2b8eb2009-04-23 21:03:4265
66# Warning message when the change appears to be missing tests.
67MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!"
68
[email protected]98fc2b92009-05-21 14:11:5169# Global cache of files cached in GetCacheDir().
70FILES_CACHE = {}
[email protected]fb2b8eb2009-04-23 21:03:4271
[email protected]4c22d722010-05-14 19:01:2272# Valid extensions for files we want to lint.
73DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
74DEFAULT_LINT_IGNORE_REGEX = r"$^"
75
[email protected]30becc22011-03-17 01:21:2276REVIEWERS_REGEX = r'\s*R=(.+)'
[email protected]4c22d722010-05-14 19:01:2277
[email protected]e5299012010-04-07 18:02:2678def CheckHomeForFile(filename):
79 """Checks the users home dir for the existence of the given file. Returns
80 the path to the file if it's there, or None if it is not.
81 """
82 home_vars = ['HOME']
83 if sys.platform in ('cygwin', 'win32'):
84 home_vars.append('USERPROFILE')
85 for home_var in home_vars:
86 home = os.getenv(home_var)
87 if home != None:
88 full_path = os.path.join(home, filename)
89 if os.path.exists(full_path):
90 return full_path
91 return None
[email protected]fb2b8eb2009-04-23 21:03:4292
[email protected]35fe9ad2010-05-25 23:59:5493
[email protected]62fd6932010-05-27 13:13:2394def UnknownFiles():
95 """Runs svn status and returns unknown files."""
96 return [item[1] for item in SVN.CaptureStatus([]) if item[0][0] == '?']
[email protected]207fdf32009-04-28 19:57:0197
98
[email protected]fb2b8eb2009-04-23 21:03:4299def GetRepositoryRoot():
100 """Returns the top level directory of the current repository.
101
102 The directory is returned as an absolute path.
103 """
[email protected]98fc2b92009-05-21 14:11:51104 global REPOSITORY_ROOT
105 if not REPOSITORY_ROOT:
[email protected]94b1ee92009-12-19 20:27:20106 REPOSITORY_ROOT = SVN.GetCheckoutRoot(os.getcwd())
107 if not REPOSITORY_ROOT:
[email protected]5f3eee32009-09-17 00:34:30108 raise gclient_utils.Error("gcl run outside of repository")
[email protected]98fc2b92009-05-21 14:11:51109 return REPOSITORY_ROOT
[email protected]fb2b8eb2009-04-23 21:03:42110
111
112def GetInfoDir():
113 """Returns the directory where gcl info files are stored."""
[email protected]374d65e2009-05-21 14:00:52114 return os.path.join(GetRepositoryRoot(), '.svn', 'gcl_info')
115
116
117def GetChangesDir():
118 """Returns the directory where gcl change files are stored."""
119 return os.path.join(GetInfoDir(), 'changes')
[email protected]fb2b8eb2009-04-23 21:03:42120
121
[email protected]98fc2b92009-05-21 14:11:51122def GetCacheDir():
123 """Returns the directory where gcl change files are stored."""
124 return os.path.join(GetInfoDir(), 'cache')
125
126
127def GetCachedFile(filename, max_age=60*60*24*3, use_root=False):
128 """Retrieves a file from the repository and caches it in GetCacheDir() for
129 max_age seconds.
130
131 use_root: If False, look up the arborescence for the first match, otherwise go
132 directory to the root repository.
133
134 Note: The cache will be inconsistent if the same file is retrieved with both
[email protected]bb816382009-10-29 01:38:02135 use_root=True and use_root=False. Don't be stupid.
[email protected]98fc2b92009-05-21 14:11:51136 """
[email protected]98fc2b92009-05-21 14:11:51137 if filename not in FILES_CACHE:
138 # Don't try to look up twice.
139 FILES_CACHE[filename] = None
[email protected]9b613272009-04-24 01:28:28140 # First we check if we have a cached version.
[email protected]a05be0b2009-06-30 19:13:02141 try:
142 cached_file = os.path.join(GetCacheDir(), filename)
[email protected]5f3eee32009-09-17 00:34:30143 except gclient_utils.Error:
[email protected]a05be0b2009-06-30 19:13:02144 return None
[email protected]98fc2b92009-05-21 14:11:51145 if (not os.path.exists(cached_file) or
[email protected]2f6a0d82010-05-12 00:03:30146 (time.time() - os.stat(cached_file).st_mtime) > max_age):
[email protected]b3b494f2010-08-31 20:40:08147 dir_info = SVN.CaptureInfo('.')
148 repo_root = dir_info['Repository Root']
[email protected]98fc2b92009-05-21 14:11:51149 if use_root:
150 url_path = repo_root
151 else:
[email protected]b3b494f2010-08-31 20:40:08152 url_path = dir_info['URL']
[email protected]9b613272009-04-24 01:28:28153 while True:
[email protected]fa44e4a2009-12-03 01:41:13154 # Look in the repository at the current level for the file.
[email protected]b6ee1672010-08-19 17:06:07155 for _ in range(5):
[email protected]b3b494f2010-08-31 20:40:08156 content = None
[email protected]b6ee1672010-08-19 17:06:07157 try:
158 # Take advantage of the fact that svn won't output to stderr in case
159 # of success but will do in case of failure so don't mind putting
160 # stderr into content_array.
161 content_array = []
[email protected]b3b494f2010-08-31 20:40:08162 svn_path = url_path + '/' + filename
[email protected]17368002010-09-01 23:36:32163 args = ['svn', 'cat', svn_path]
[email protected]02913a52010-08-25 20:50:50164 if sys.platform != 'darwin':
165 # MacOSX 10.5.2 has a bug with svn 1.4.4 that will trigger the
166 # 'Can\'t get username or password' and can be fixed easily.
167 # The fix doesn't work if the user upgraded to svn 1.6.x. Bleh.
168 # I don't have time to fix their broken stuff.
169 args.append('--non-interactive')
[email protected]17d01792010-09-01 18:07:10170 gclient_utils.CheckCallAndFilter(
171 args, cwd='.', filter_fn=content_array.append)
[email protected]b6ee1672010-08-19 17:06:07172 # Exit the loop if the file was found. Override content.
173 content = '\n'.join(content_array)
174 break
[email protected]2b9aa8e2010-08-25 20:01:42175 except gclient_utils.Error:
[email protected]b6ee1672010-08-19 17:06:07176 if content_array[0].startswith(
177 'svn: Can\'t get username or password'):
178 ErrorExit('Your svn credentials expired. Please run svn update '
179 'to fix the cached credentials')
[email protected]3c842982010-08-20 17:26:49180 if content_array[0].startswith('svn: Can\'t get password'):
181 ErrorExit('If are using a Mac and svn --version shows 1.4.x, '
182 'please hack gcl.py to remove --non-interactive usage, it\'s'
183 'a bug on your installed copy')
[email protected]dcd15222010-12-21 22:43:19184 if (content_array[0].startswith('svn: File not found:') or
185 content_array[0].endswith('path not found')):
186 break
187 # Otherwise, fall through to trying again.
[email protected]b6ee1672010-08-19 17:06:07188 if content:
[email protected]9b613272009-04-24 01:28:28189 break
[email protected]9b613272009-04-24 01:28:28190 if url_path == repo_root:
191 # Reached the root. Abandoning search.
[email protected]98fc2b92009-05-21 14:11:51192 break
[email protected]9b613272009-04-24 01:28:28193 # Go up one level to try again.
194 url_path = os.path.dirname(url_path)
[email protected]b3b494f2010-08-31 20:40:08195 if content is not None or filename != CODEREVIEW_SETTINGS_FILE:
196 # Write a cached version even if there isn't a file, so we don't try to
197 # fetch it each time. codereview.settings must always be present so do
198 # not cache negative.
199 gclient_utils.FileWrite(cached_file, content or '')
[email protected]98fc2b92009-05-21 14:11:51200 else:
[email protected]0fca4f32009-12-18 15:14:34201 content = gclient_utils.FileRead(cached_file, 'r')
[email protected]98fc2b92009-05-21 14:11:51202 # Keep the content cached in memory.
203 FILES_CACHE[filename] = content
204 return FILES_CACHE[filename]
[email protected]9b613272009-04-24 01:28:28205
[email protected]98fc2b92009-05-21 14:11:51206
207def GetCodeReviewSetting(key):
208 """Returns a value for the given key for this repository."""
209 # Use '__just_initialized' as a flag to determine if the settings were
210 # already initialized.
[email protected]98fc2b92009-05-21 14:11:51211 if '__just_initialized' not in CODEREVIEW_SETTINGS:
[email protected]b0442182009-06-05 14:20:47212 settings_file = GetCachedFile(CODEREVIEW_SETTINGS_FILE)
213 if settings_file:
214 for line in settings_file.splitlines():
[email protected]807c4462010-07-10 00:45:28215 if not line or line.startswith('#'):
[email protected]b0442182009-06-05 14:20:47216 continue
[email protected]807c4462010-07-10 00:45:28217 if not ':' in line:
218 raise gclient_utils.Error(
219 '%s is invalid, please fix. It\'s content:\n\n%s' %
220 (CODEREVIEW_SETTINGS_FILE, settings_file))
[email protected]dd218e52010-08-23 13:02:41221 k, v = line.split(':', 1)
222 CODEREVIEW_SETTINGS[k.strip()] = v.strip()
[email protected]98fc2b92009-05-21 14:11:51223 CODEREVIEW_SETTINGS.setdefault('__just_initialized', None)
[email protected]fb2b8eb2009-04-23 21:03:42224 return CODEREVIEW_SETTINGS.get(key, "")
225
226
[email protected]fb2b8eb2009-04-23 21:03:42227def Warn(msg):
[email protected]6e29d572010-06-04 17:32:20228 print >> sys.stderr, msg
[email protected]223b7192010-06-04 18:52:58229
230
231def ErrorExit(msg):
232 print >> sys.stderr, msg
233 sys.exit(1)
[email protected]fb2b8eb2009-04-23 21:03:42234
235
236def RunShellWithReturnCode(command, print_output=False):
237 """Executes a command and returns the output and the return code."""
[email protected]3a292682010-08-23 18:54:55238 p = gclient_utils.Popen(command, stdout=subprocess.PIPE,
239 stderr=subprocess.STDOUT, universal_newlines=True)
[email protected]fb2b8eb2009-04-23 21:03:42240 if print_output:
241 output_array = []
242 while True:
243 line = p.stdout.readline()
244 if not line:
245 break
246 if print_output:
247 print line.strip('\n')
248 output_array.append(line)
249 output = "".join(output_array)
250 else:
251 output = p.stdout.read()
252 p.wait()
253 p.stdout.close()
254 return output, p.returncode
255
256
257def RunShell(command, print_output=False):
258 """Executes a command and returns the output."""
259 return RunShellWithReturnCode(command, print_output)[0]
260
261
[email protected]51ee0072009-06-08 19:20:05262def FilterFlag(args, flag):
263 """Returns True if the flag is present in args list.
264
265 The flag is removed from args if present.
266 """
267 if flag in args:
268 args.remove(flag)
269 return True
270 return False
271
272
[email protected]be0d1ca2009-05-12 19:23:02273class ChangeInfo(object):
[email protected]fb2b8eb2009-04-23 21:03:42274 """Holds information about a changelist.
275
[email protected]32ba2602009-06-06 18:44:48276 name: change name.
277 issue: the Rietveld issue number or 0 if it hasn't been uploaded yet.
278 patchset: the Rietveld latest patchset number or 0.
[email protected]fb2b8eb2009-04-23 21:03:42279 description: the description.
280 files: a list of 2 tuple containing (status, filename) of changed files,
281 with paths being relative to the top repository directory.
[email protected]8d5c9a52009-06-12 15:59:08282 local_root: Local root directory
[email protected]bf1fdca2010-11-01 18:05:36283 rietveld: rietveld server for this change
[email protected]fb2b8eb2009-04-23 21:03:42284 """
[email protected]bf1fdca2010-11-01 18:05:36285 # Kept for unit test support. This is for the old format, it's deprecated.
[email protected]32ba2602009-06-06 18:44:48286 _SEPARATOR = "\n-----\n"
[email protected]32ba2602009-06-06 18:44:48287
[email protected]ea452b32009-11-22 20:04:31288 def __init__(self, name, issue, patchset, description, files, local_root,
[email protected]bf1fdca2010-11-01 18:05:36289 rietveld, needs_upload=False):
[email protected]fb2b8eb2009-04-23 21:03:42290 self.name = name
[email protected]32ba2602009-06-06 18:44:48291 self.issue = int(issue)
292 self.patchset = int(patchset)
[email protected]30becc22011-03-17 01:21:22293 self._description = None
294 self._subject = None
295 self._reviewers = None
296 self._set_description(description)
[email protected]be0d1ca2009-05-12 19:23:02297 if files is None:
298 files = []
[email protected]17f59f22009-06-12 13:27:24299 self._files = files
[email protected]fb2b8eb2009-04-23 21:03:42300 self.patch = None
[email protected]8d5c9a52009-06-12 15:59:08301 self._local_root = local_root
[email protected]ea452b32009-11-22 20:04:31302 self.needs_upload = needs_upload
[email protected]bf1fdca2010-11-01 18:05:36303 self.rietveld = rietveld
304 if not self.rietveld:
305 # Set the default value.
306 self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER')
[email protected]ea452b32009-11-22 20:04:31307
[email protected]30becc22011-03-17 01:21:22308 def _get_description(self):
309 return self._description
310
311 def _set_description(self, description):
312 # TODO(dpranke): Cloned from git_cl.py. These should be shared.
313 if not description:
314 self._description = description
315 return
316
317 parsed_lines = []
318 reviewers_re = re.compile(REVIEWERS_REGEX)
319 reviewers = ''
320 subject = ''
321 for l in description.splitlines():
322 if not subject:
323 subject = l
324 matched_reviewers = reviewers_re.match(l)
325 if matched_reviewers:
326 reviewers = matched_reviewers.group(1).split(',')
327 parsed_lines.append(l)
328
329 if len(subject) > 100:
330 subject = subject[:97] + '...'
331
332 self._subject = subject
333 self._reviewers = reviewers
334 self._description = '\n'.join(parsed_lines)
335
336 description = property(_get_description, _set_description)
337
338 @property
339 def reviewers(self):
340 return self._reviewers
341
342 @property
343 def subject(self):
344 return self._subject
345
[email protected]ea452b32009-11-22 20:04:31346 def NeedsUpload(self):
347 return self.needs_upload
[email protected]fb2b8eb2009-04-23 21:03:42348
[email protected]17f59f22009-06-12 13:27:24349 def GetFileNames(self):
350 """Returns the list of file names included in this change."""
[email protected]e3608df2009-11-10 20:22:57351 return [f[1] for f in self._files]
[email protected]17f59f22009-06-12 13:27:24352
353 def GetFiles(self):
354 """Returns the list of files included in this change with their status."""
355 return self._files
356
357 def GetLocalRoot(self):
358 """Returns the local repository checkout root directory."""
359 return self._local_root
[email protected]fb2b8eb2009-04-23 21:03:42360
[email protected]f0dfba32009-08-07 22:03:37361 def Exists(self):
362 """Returns True if this change already exists (i.e., is not new)."""
363 return (self.issue or self.description or self._files)
364
[email protected]fb2b8eb2009-04-23 21:03:42365 def _NonDeletedFileList(self):
366 """Returns a list of files in this change, not including deleted files."""
[email protected]e3608df2009-11-10 20:22:57367 return [f[1] for f in self.GetFiles()
368 if not f[0].startswith("D")]
[email protected]fb2b8eb2009-04-23 21:03:42369
370 def _AddedFileList(self):
371 """Returns a list of files added in this change."""
[email protected]e3608df2009-11-10 20:22:57372 return [f[1] for f in self.GetFiles() if f[0].startswith("A")]
[email protected]fb2b8eb2009-04-23 21:03:42373
374 def Save(self):
375 """Writes the changelist information to disk."""
[email protected]fa3843e2010-11-01 13:34:37376 data = json.dumps({
377 'issue': self.issue,
378 'patchset': self.patchset,
379 'needs_upload': self.NeedsUpload(),
380 'files': self.GetFiles(),
381 'description': self.description,
[email protected]bf1fdca2010-11-01 18:05:36382 'rietveld': self.rietveld,
[email protected]fa3843e2010-11-01 13:34:37383 }, sort_keys=True, indent=2)
[email protected]fc83c112009-12-18 15:14:10384 gclient_utils.FileWrite(GetChangelistInfoFile(self.name), data)
[email protected]fb2b8eb2009-04-23 21:03:42385
386 def Delete(self):
387 """Removes the changelist information from disk."""
388 os.remove(GetChangelistInfoFile(self.name))
389
390 def CloseIssue(self):
391 """Closes the Rietveld issue for this changelist."""
[email protected]41db6ef2010-11-17 19:42:34392 # Newer versions of Rietveld require us to pass an XSRF token to POST, so
393 # we fetch it from the server.
394 xsrf_token = self.SendToRietveld(
395 '/xsrf_token',
396 extra_headers={'X-Requesting-XSRF-Token': '1'})
397
398 # You cannot close an issue with a GET.
399 # We pass an empty string for the data so it is a POST rather than a GET.
400 data = [("description", self.description),
401 ("xsrf_token", xsrf_token)]
[email protected]fb2b8eb2009-04-23 21:03:42402 ctype, body = upload.EncodeMultipartFormData(data, [])
[email protected]41db6ef2010-11-17 19:42:34403 self.SendToRietveld('/%d/close' % self.issue, payload=body,
404 content_type=ctype)
[email protected]fb2b8eb2009-04-23 21:03:42405
406 def UpdateRietveldDescription(self):
407 """Sets the description for an issue on Rietveld."""
408 data = [("description", self.description),]
409 ctype, body = upload.EncodeMultipartFormData(data, [])
[email protected]41db6ef2010-11-17 19:42:34410 self.SendToRietveld('/%d/description' % self.issue, payload=body,
411 content_type=ctype)
[email protected]bf1fdca2010-11-01 18:05:36412
413 def GetIssueDescription(self):
414 """Returns the issue description from Rietveld."""
415 return self.SendToRietveld('/%d/description' % self.issue)
416
417 def PrimeLint(self):
418 """Do background work on Rietveld to lint the file so that the results are
419 ready when the issue is viewed."""
420 if self.issue and self.patchset:
421 self.SendToRietveld('/lint/issue%s_%s' % (self.issue, self.patchset),
422 timeout=1)
423
[email protected]41db6ef2010-11-17 19:42:34424 def SendToRietveld(self, request_path, timeout=None, **kwargs):
[email protected]bf1fdca2010-11-01 18:05:36425 """Send a POST/GET to Rietveld. Returns the response body."""
426 if not self.rietveld:
427 ErrorExit(CODEREVIEW_SETTINGS_FILE_NOT_FOUND)
428 def GetUserCredentials():
429 """Prompts the user for a username and password."""
430 email = upload.GetEmail('Email (login for uploading to %s)' %
431 self.rietveld)
432 password = getpass.getpass('Password for %s: ' % email)
433 return email, password
434 rpc_server = upload.HttpRpcServer(self.rietveld,
435 GetUserCredentials,
436 save_cookies=True)
437 try:
[email protected]41db6ef2010-11-17 19:42:34438 return rpc_server.Send(request_path, timeout=timeout, **kwargs)
[email protected]bf1fdca2010-11-01 18:05:36439 except urllib2.URLError:
440 if timeout is None:
441 ErrorExit('Error accessing url %s' % request_path)
442 else:
443 return None
[email protected]fb2b8eb2009-04-23 21:03:42444
445 def MissingTests(self):
446 """Returns True if the change looks like it needs unit tests but has none.
447
448 A change needs unit tests if it contains any new source files or methods.
449 """
450 SOURCE_SUFFIXES = [".cc", ".cpp", ".c", ".m", ".mm"]
451 # Ignore third_party entirely.
[email protected]e3608df2009-11-10 20:22:57452 files = [f for f in self._NonDeletedFileList()
453 if f.find("third_party") == -1]
454 added_files = [f for f in self._AddedFileList()
455 if f.find("third_party") == -1]
[email protected]fb2b8eb2009-04-23 21:03:42456
457 # If the change is entirely in third_party, we're done.
458 if len(files) == 0:
459 return False
460
461 # Any new or modified test files?
462 # A test file's name ends with "test.*" or "tests.*".
463 test_files = [test for test in files
464 if os.path.splitext(test)[0].rstrip("s").endswith("test")]
465 if len(test_files) > 0:
466 return False
467
468 # Any new source files?
[email protected]e3608df2009-11-10 20:22:57469 source_files = [item for item in added_files
470 if os.path.splitext(item)[1] in SOURCE_SUFFIXES]
[email protected]fb2b8eb2009-04-23 21:03:42471 if len(source_files) > 0:
472 return True
473
474 # Do the long test, checking the files for new methods.
475 return self._HasNewMethod()
476
477 def _HasNewMethod(self):
478 """Returns True if the changeset contains any new functions, or if a
479 function signature has been changed.
480
481 A function is identified by starting flush left, containing a "(" before
482 the next flush-left line, and either ending with "{" before the next
483 flush-left line or being followed by an unindented "{".
484
485 Currently this returns True for new methods, new static functions, and
486 methods or functions whose signatures have been changed.
487
488 Inline methods added to header files won't be detected by this. That's
489 acceptable for purposes of determining if a unit test is needed, since
490 inline methods should be trivial.
491 """
492 # To check for methods added to source or header files, we need the diffs.
493 # We'll generate them all, since there aren't likely to be many files
494 # apart from source and headers; besides, we'll want them all if we're
495 # uploading anyway.
496 if self.patch is None:
[email protected]17f59f22009-06-12 13:27:24497 self.patch = GenerateDiff(self.GetFileNames())
[email protected]fb2b8eb2009-04-23 21:03:42498
499 definition = ""
500 for line in self.patch.splitlines():
501 if not line.startswith("+"):
502 continue
503 line = line.strip("+").rstrip(" \t")
504 # Skip empty lines, comments, and preprocessor directives.
505 # TODO(pamg): Handle multiline comments if it turns out to be a problem.
506 if line == "" or line.startswith("/") or line.startswith("#"):
507 continue
508
509 # A possible definition ending with "{" is complete, so check it.
510 if definition.endswith("{"):
511 if definition.find("(") != -1:
512 return True
513 definition = ""
514
515 # A { or an indented line, when we're in a definition, continues it.
516 if (definition != "" and
517 (line == "{" or line.startswith(" ") or line.startswith("\t"))):
518 definition += line
519
520 # A flush-left line starts a new possible function definition.
521 elif not line.startswith(" ") and not line.startswith("\t"):
522 definition = line
523
524 return False
525
[email protected]32ba2602009-06-06 18:44:48526 @staticmethod
[email protected]8d5c9a52009-06-12 15:59:08527 def Load(changename, local_root, fail_on_not_found, update_status):
[email protected]32ba2602009-06-06 18:44:48528 """Gets information about a changelist.
[email protected]fb2b8eb2009-04-23 21:03:42529
[email protected]32ba2602009-06-06 18:44:48530 Args:
531 fail_on_not_found: if True, this function will quit the program if the
532 changelist doesn't exist.
533 update_status: if True, the svn status will be updated for all the files
534 and unchanged files will be removed.
535
536 Returns: a ChangeInfo object.
537 """
538 info_file = GetChangelistInfoFile(changename)
539 if not os.path.exists(info_file):
540 if fail_on_not_found:
541 ErrorExit("Changelist " + changename + " not found.")
[email protected]bf1fdca2010-11-01 18:05:36542 return ChangeInfo(changename, 0, 0, '', None, local_root, rietveld=None,
[email protected]ea452b32009-11-22 20:04:31543 needs_upload=False)
[email protected]8a8ea022010-11-01 13:27:32544 content = gclient_utils.FileRead(info_file, 'r')
545 save = False
546 try:
[email protected]fa3843e2010-11-01 13:34:37547 values = ChangeInfo._LoadNewFormat(content)
[email protected]8a8ea022010-11-01 13:27:32548 except ValueError:
[email protected]fa3843e2010-11-01 13:34:37549 try:
550 values = ChangeInfo._LoadOldFormat(content)
551 save = True
552 except ValueError:
553 ErrorExit(
554 ('Changelist file %s is corrupt.\n'
555 'Either run "gcl delete %s" or manually edit the file') % (
556 info_file, changename))
[email protected]8a8ea022010-11-01 13:27:32557 files = values['files']
[email protected]32ba2602009-06-06 18:44:48558 if update_status:
[email protected]79b7ef02010-11-01 13:25:13559 for item in files[:]:
[email protected]e3608df2009-11-10 20:22:57560 filename = os.path.join(local_root, item[1])
[email protected]5aeb7dd2009-11-17 18:09:01561 status_result = SVN.CaptureStatus(filename)
[email protected]32ba2602009-06-06 18:44:48562 if not status_result or not status_result[0][0]:
563 # File has been reverted.
564 save = True
[email protected]e3608df2009-11-10 20:22:57565 files.remove(item)
[email protected]32ba2602009-06-06 18:44:48566 continue
567 status = status_result[0][0]
[email protected]e3608df2009-11-10 20:22:57568 if status != item[0]:
[email protected]32ba2602009-06-06 18:44:48569 save = True
[email protected]e3608df2009-11-10 20:22:57570 files[files.index(item)] = (status, item[1])
[email protected]8a8ea022010-11-01 13:27:32571 change_info = ChangeInfo(changename, values['issue'], values['patchset'],
572 values['description'], files,
[email protected]bf1fdca2010-11-01 18:05:36573 local_root, values.get('rietveld'),
574 values['needs_upload'])
[email protected]32ba2602009-06-06 18:44:48575 if save:
576 change_info.Save()
577 return change_info
[email protected]fb2b8eb2009-04-23 21:03:42578
[email protected]8a8ea022010-11-01 13:27:32579 @staticmethod
580 def _LoadOldFormat(content):
[email protected]bf1fdca2010-11-01 18:05:36581 # The info files have the following format:
582 # issue_id, patchset\n (, patchset is optional)
583 # _SEPARATOR\n
584 # filepath1\n
585 # filepath2\n
586 # .
587 # .
588 # filepathn\n
589 # _SEPARATOR\n
590 # description
[email protected]8a8ea022010-11-01 13:27:32591 split_data = content.split(ChangeInfo._SEPARATOR, 2)
592 if len(split_data) != 3:
593 raise ValueError('Bad change format')
594 values = {
595 'issue': 0,
596 'patchset': 0,
597 'needs_upload': False,
598 'files': [],
599 }
600 items = split_data[0].split(', ')
601 if items[0]:
602 values['issue'] = int(items[0])
603 if len(items) > 1:
604 values['patchset'] = int(items[1])
605 if len(items) > 2:
606 values['needs_upload'] = (items[2] == "dirty")
607 for line in split_data[1].splitlines():
608 status = line[:7]
609 filename = line[7:]
610 values['files'].append((status, filename))
611 values['description'] = split_data[2]
612 return values
613
[email protected]fa3843e2010-11-01 13:34:37614 @staticmethod
615 def _LoadNewFormat(content):
616 return json.loads(content)
617
[email protected]fb2b8eb2009-04-23 21:03:42618
619def GetChangelistInfoFile(changename):
620 """Returns the file that stores information about a changelist."""
621 if not changename or re.search(r'[^\w-]', changename):
622 ErrorExit("Invalid changelist name: " + changename)
[email protected]374d65e2009-05-21 14:00:52623 return os.path.join(GetChangesDir(), changename)
[email protected]fb2b8eb2009-04-23 21:03:42624
625
[email protected]8d5c9a52009-06-12 15:59:08626def LoadChangelistInfoForMultiple(changenames, local_root, fail_on_not_found,
627 update_status):
[email protected]fb2b8eb2009-04-23 21:03:42628 """Loads many changes and merge their files list into one pseudo change.
629
630 This is mainly usefull to concatenate many changes into one for a 'gcl try'.
631 """
632 changes = changenames.split(',')
[email protected]ea452b32009-11-22 20:04:31633 aggregate_change_info = ChangeInfo(changenames, 0, 0, '', None, local_root,
[email protected]bf1fdca2010-11-01 18:05:36634 rietveld=None, needs_upload=False)
[email protected]fb2b8eb2009-04-23 21:03:42635 for change in changes:
[email protected]8d5c9a52009-06-12 15:59:08636 aggregate_change_info._files += ChangeInfo.Load(change,
637 local_root,
638 fail_on_not_found,
[email protected]17f59f22009-06-12 13:27:24639 update_status).GetFiles()
[email protected]fb2b8eb2009-04-23 21:03:42640 return aggregate_change_info
641
642
[email protected]fb2b8eb2009-04-23 21:03:42643def GetCLs():
644 """Returns a list of all the changelists in this repository."""
[email protected]374d65e2009-05-21 14:00:52645 cls = os.listdir(GetChangesDir())
[email protected]fb2b8eb2009-04-23 21:03:42646 if CODEREVIEW_SETTINGS_FILE in cls:
647 cls.remove(CODEREVIEW_SETTINGS_FILE)
648 return cls
649
650
651def GenerateChangeName():
652 """Generate a random changelist name."""
653 random.seed()
654 current_cl_names = GetCLs()
655 while True:
656 cl_name = (random.choice(string.ascii_lowercase) +
657 random.choice(string.digits) +
658 random.choice(string.ascii_lowercase) +
659 random.choice(string.digits))
660 if cl_name not in current_cl_names:
661 return cl_name
662
663
664def GetModifiedFiles():
665 """Returns a set that maps from changelist name to (status,filename) tuples.
666
667 Files not in a changelist have an empty changelist name. Filenames are in
668 relation to the top level directory of the current repository. Note that
669 only the current directory and subdirectories are scanned, in order to
670 improve performance while still being flexible.
671 """
672 files = {}
673
674 # Since the files are normalized to the root folder of the repositary, figure
675 # out what we need to add to the paths.
676 dir_prefix = os.getcwd()[len(GetRepositoryRoot()):].strip(os.sep)
677
678 # Get a list of all files in changelists.
679 files_in_cl = {}
680 for cl in GetCLs():
[email protected]8d5c9a52009-06-12 15:59:08681 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(),
682 fail_on_not_found=True, update_status=False)
[email protected]17f59f22009-06-12 13:27:24683 for status, filename in change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42684 files_in_cl[filename] = change_info.name
685
686 # Get all the modified files.
[email protected]5aeb7dd2009-11-17 18:09:01687 status_result = SVN.CaptureStatus(None)
[email protected]207fdf32009-04-28 19:57:01688 for line in status_result:
689 status = line[0]
690 filename = line[1]
691 if status[0] == "?":
[email protected]fb2b8eb2009-04-23 21:03:42692 continue
[email protected]fb2b8eb2009-04-23 21:03:42693 if dir_prefix:
694 filename = os.path.join(dir_prefix, filename)
695 change_list_name = ""
696 if filename in files_in_cl:
697 change_list_name = files_in_cl[filename]
698 files.setdefault(change_list_name, []).append((status, filename))
699
700 return files
701
702
703def GetFilesNotInCL():
704 """Returns a list of tuples (status,filename) that aren't in any changelists.
705
706 See docstring of GetModifiedFiles for information about path of files and
707 which directories are scanned.
708 """
709 modified_files = GetModifiedFiles()
710 if "" not in modified_files:
711 return []
712 return modified_files[""]
713
714
[email protected]4c22d722010-05-14 19:01:22715def ListFiles(show_unknown_files):
[email protected]fb2b8eb2009-04-23 21:03:42716 files = GetModifiedFiles()
717 cl_keys = files.keys()
718 cl_keys.sort()
719 for cl_name in cl_keys:
[email protected]88c32d82009-10-12 18:24:05720 if not cl_name:
721 continue
722 note = ""
723 change_info = ChangeInfo.Load(cl_name, GetRepositoryRoot(),
724 fail_on_not_found=True, update_status=False)
725 if len(change_info.GetFiles()) != len(files[cl_name]):
726 note = " (Note: this changelist contains files outside this directory)"
727 print "\n--- Changelist " + cl_name + note + ":"
[email protected]e3608df2009-11-10 20:22:57728 for filename in files[cl_name]:
729 print "".join(filename)
[email protected]88c32d82009-10-12 18:24:05730 if show_unknown_files:
[email protected]62fd6932010-05-27 13:13:23731 unknown_files = UnknownFiles()
[email protected]88c32d82009-10-12 18:24:05732 if (files.get('') or (show_unknown_files and len(unknown_files))):
733 print "\n--- Not in any changelist:"
[email protected]e3608df2009-11-10 20:22:57734 for item in files.get('', []):
735 print "".join(item)
[email protected]88c32d82009-10-12 18:24:05736 if show_unknown_files:
[email protected]e3608df2009-11-10 20:22:57737 for filename in unknown_files:
738 print "? %s" % filename
[email protected]4c22d722010-05-14 19:01:22739 return 0
[email protected]fb2b8eb2009-04-23 21:03:42740
741
[email protected]fb2b8eb2009-04-23 21:03:42742def GetEditor():
743 editor = os.environ.get("SVN_EDITOR")
744 if not editor:
745 editor = os.environ.get("EDITOR")
746
747 if not editor:
748 if sys.platform.startswith("win"):
749 editor = "notepad"
750 else:
751 editor = "vi"
752
753 return editor
754
755
756def GenerateDiff(files, root=None):
[email protected]f2f9d552009-12-22 00:12:57757 return SVN.GenerateDiff(files, root=root)
[email protected]fb2b8eb2009-04-23 21:03:42758
[email protected]51ee0072009-06-08 19:20:05759
760def OptionallyDoPresubmitChecks(change_info, committing, args):
761 if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"):
[email protected]30becc22011-03-17 01:21:22762 return presubmit_support.PresubmitOutput()
[email protected]b0dfd352009-06-10 14:12:54763 return DoPresubmitChecks(change_info, committing, True)
[email protected]51ee0072009-06-08 19:20:05764
765
[email protected]30becc22011-03-17 01:21:22766def suggest_reviewers(change_info, affected_files):
767 owners_db = owners.Database(change_info.GetLocalRoot(), fopen=file,
768 os_path=os.path)
769 return owners_db.reviewers_for([f[1] for f in affected_files])
770
771
[email protected]62fd6932010-05-27 13:13:23772def defer_attributes(a, b):
773 """Copy attributes from an object (like a function) to another."""
774 for x in dir(a):
775 if not getattr(b, x, None):
776 setattr(b, x, getattr(a, x))
777
778
[email protected]35fe9ad2010-05-25 23:59:54779def need_change(function):
780 """Converts args -> change_info."""
[email protected]3a174252010-10-29 15:54:51781 # pylint: disable=W0612,W0621
[email protected]35fe9ad2010-05-25 23:59:54782 def hook(args):
783 if not len(args) == 1:
784 ErrorExit("You need to pass a change list name")
785 change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(), True, True)
786 return function(change_info)
[email protected]62fd6932010-05-27 13:13:23787 defer_attributes(function, hook)
788 hook.need_change = True
789 hook.no_args = True
[email protected]35fe9ad2010-05-25 23:59:54790 return hook
791
792
[email protected]62fd6932010-05-27 13:13:23793def need_change_and_args(function):
794 """Converts args -> change_info."""
[email protected]3a174252010-10-29 15:54:51795 # pylint: disable=W0612,W0621
[email protected]62fd6932010-05-27 13:13:23796 def hook(args):
[email protected]e56fe822010-05-28 20:36:57797 if not args:
798 ErrorExit("You need to pass a change list name")
[email protected]62fd6932010-05-27 13:13:23799 change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
800 return function(change_info, args)
801 defer_attributes(function, hook)
802 hook.need_change = True
803 return hook
804
805
806def no_args(function):
807 """Make sure no args are passed."""
[email protected]3a174252010-10-29 15:54:51808 # pylint: disable=W0612,W0621
[email protected]62fd6932010-05-27 13:13:23809 def hook(args):
810 if args:
811 ErrorExit("Doesn't support arguments")
812 return function()
813 defer_attributes(function, hook)
814 hook.no_args = True
815 return hook
816
817
818def attrs(**kwargs):
819 """Decorate a function with new attributes."""
820 def decorate(function):
821 for k in kwargs:
822 setattr(function, k, kwargs[k])
823 return function
824 return decorate
825
826
827@no_args
828def CMDopened():
829 """Lists modified files in the current directory down."""
830 return ListFiles(False)
831
832
833@no_args
834def CMDstatus():
835 """Lists modified and unknown files in the current directory down."""
836 return ListFiles(True)
837
838
839@need_change_and_args
[email protected]2b9aa8e2010-08-25 20:01:42840@attrs(usage='[--no_presubmit] [--no_watchlists]')
[email protected]62fd6932010-05-27 13:13:23841def CMDupload(change_info, args):
842 """Uploads the changelist to the server for review.
843
[email protected]5db5ba52010-07-20 15:50:47844 This does not submit a try job; use gcl try to submit a try job.
[email protected]62fd6932010-05-27 13:13:23845 """
[email protected]10ccd112010-08-24 16:48:42846 if '-s' in args or '--server' in args:
847 ErrorExit('Don\'t use the -s flag, fix codereview.settings instead')
[email protected]17f59f22009-06-12 13:27:24848 if not change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42849 print "Nothing to upload, changelist is empty."
[email protected]35fe9ad2010-05-25 23:59:54850 return 0
[email protected]30becc22011-03-17 01:21:22851
852 output = OptionallyDoPresubmitChecks(change_info, False, args)
853 if not output.should_continue():
[email protected]35fe9ad2010-05-25 23:59:54854 return 1
[email protected]62fd6932010-05-27 13:13:23855 no_watchlists = (FilterFlag(args, "--no_watchlists") or
856 FilterFlag(args, "--no-watchlists"))
[email protected]fb2b8eb2009-04-23 21:03:42857
858 # Map --send-mail to --send_mail
[email protected]51ee0072009-06-08 19:20:05859 if FilterFlag(args, "--send-mail"):
[email protected]fb2b8eb2009-04-23 21:03:42860 args.append("--send_mail")
861
[email protected]fb2b8eb2009-04-23 21:03:42862 upload_arg = ["upload.py", "-y"]
[email protected]bf1fdca2010-11-01 18:05:36863 upload_arg.append("--server=%s" % change_info.rietveld)
[email protected]30becc22011-03-17 01:21:22864
865 reviewers = change_info.reviewers or output.reviewers
866 if (reviewers and
867 not any(arg.startswith('-r') or arg.startswith('--reviewer') for
868 arg in args)):
869 upload_arg.append('--reviewers=%s' % ','.join(reviewers))
870
[email protected]fb2b8eb2009-04-23 21:03:42871 upload_arg.extend(args)
872
873 desc_file = ""
874 if change_info.issue: # Uploading a new patchset.
875 found_message = False
876 for arg in args:
877 if arg.startswith("--message") or arg.startswith("-m"):
878 found_message = True
879 break
880
881 if not found_message:
882 upload_arg.append("--message=''")
883
[email protected]32ba2602009-06-06 18:44:48884 upload_arg.append("--issue=%d" % change_info.issue)
[email protected]fb2b8eb2009-04-23 21:03:42885 else: # First time we upload.
886 handle, desc_file = tempfile.mkstemp(text=True)
887 os.write(handle, change_info.description)
888 os.close(handle)
889
[email protected]b2ab4942009-06-11 21:39:19890 # Watchlist processing -- CC people interested in this changeset
891 # http://dev.chromium.org/developers/contributing-code/watchlists
892 if not no_watchlists:
893 import watchlists
[email protected]17f59f22009-06-12 13:27:24894 watchlist = watchlists.Watchlists(change_info.GetLocalRoot())
[email protected]07f01862009-06-12 16:51:08895 watchers = watchlist.GetWatchersForPaths(change_info.GetFileNames())
[email protected]b2ab4942009-06-11 21:39:19896
[email protected]fb2b8eb2009-04-23 21:03:42897 cc_list = GetCodeReviewSetting("CC_LIST")
[email protected]b2ab4942009-06-11 21:39:19898 if not no_watchlists and watchers:
[email protected]6e29d572010-06-04 17:32:20899 # Filter out all empty elements and join by ','
900 cc_list = ','.join(filter(None, [cc_list] + watchers))
[email protected]fb2b8eb2009-04-23 21:03:42901 if cc_list:
902 upload_arg.append("--cc=" + cc_list)
903 upload_arg.append("--description_file=" + desc_file + "")
[email protected]30becc22011-03-17 01:21:22904 if change_info.subject:
[email protected]b68786e2011-03-17 01:39:09905 upload_arg.append("--message=" + change_info.subject)
[email protected]fb2b8eb2009-04-23 21:03:42906
[email protected]83b6e4b2010-03-09 03:16:14907 if GetCodeReviewSetting("PRIVATE") == "True":
908 upload_arg.append("--private")
909
[email protected]fb2b8eb2009-04-23 21:03:42910 # Change the current working directory before calling upload.py so that it
911 # shows the correct base.
912 previous_cwd = os.getcwd()
[email protected]17f59f22009-06-12 13:27:24913 os.chdir(change_info.GetLocalRoot())
[email protected]fb2b8eb2009-04-23 21:03:42914 # If we have a lot of files with long paths, then we won't be able to fit
915 # the command to "svn diff". Instead, we generate the diff manually for
916 # each file and concatenate them before passing it to upload.py.
917 if change_info.patch is None:
[email protected]17f59f22009-06-12 13:27:24918 change_info.patch = GenerateDiff(change_info.GetFileNames())
[email protected]fb2b8eb2009-04-23 21:03:42919 issue, patchset = upload.RealMain(upload_arg, change_info.patch)
[email protected]32ba2602009-06-06 18:44:48920 if issue and patchset:
921 change_info.issue = int(issue)
922 change_info.patchset = int(patchset)
[email protected]fb2b8eb2009-04-23 21:03:42923 change_info.Save()
924
925 if desc_file:
926 os.remove(desc_file)
[email protected]bf1fdca2010-11-01 18:05:36927 change_info.PrimeLint()
[email protected]57e78552009-09-11 23:04:30928 os.chdir(previous_cwd)
[email protected]02287952010-07-23 22:36:58929 print "*** Upload does not submit a try; use gcl try to submit a try. ***"
[email protected]35fe9ad2010-05-25 23:59:54930 return 0
[email protected]fb2b8eb2009-04-23 21:03:42931
[email protected]fb2b8eb2009-04-23 21:03:42932
[email protected]8e13a092010-11-02 19:06:06933@need_change_and_args
934@attrs(usage='[--upload]')
935def CMDpresubmit(change_info, args):
[email protected]62fd6932010-05-27 13:13:23936 """Runs presubmit checks on the change.
937
938 The actual presubmit code is implemented in presubmit_support.py and looks
939 for PRESUBMIT.py files."""
[email protected]17f59f22009-06-12 13:27:24940 if not change_info.GetFiles():
[email protected]8e13a092010-11-02 19:06:06941 print('Nothing to presubmit check, changelist is empty.')
[email protected]4c22d722010-05-14 19:01:22942 return 0
[email protected]8e13a092010-11-02 19:06:06943 parser = optparse.OptionParser()
944 parser.add_option('--upload', action='store_true')
945 options, args = parser.parse_args(args)
946 if args:
947 parser.error('Unrecognized args: %s' % args)
948 if options.upload:
949 print('*** Presubmit checks for UPLOAD would report: ***')
950 return not DoPresubmitChecks(change_info, False, False)
951 else:
952 print('*** Presubmit checks for COMMIT would report: ***')
953 return not DoPresubmitChecks(change_info, True, False)
[email protected]fb2b8eb2009-04-23 21:03:42954
955
956def TryChange(change_info, args, swallow_exception):
957 """Create a diff file of change_info and send it to the try server."""
958 try:
959 import trychange
960 except ImportError:
961 if swallow_exception:
[email protected]35fe9ad2010-05-25 23:59:54962 return 1
[email protected]fb2b8eb2009-04-23 21:03:42963 ErrorExit("You need to install trychange.py to use the try server.")
964
[email protected]18111352009-12-20 17:21:28965 trychange_args = []
[email protected]fb2b8eb2009-04-23 21:03:42966 if change_info:
[email protected]18111352009-12-20 17:21:28967 trychange_args.extend(['--name', change_info.name])
[email protected]32ba2602009-06-06 18:44:48968 if change_info.issue:
969 trychange_args.extend(["--issue", str(change_info.issue)])
970 if change_info.patchset:
971 trychange_args.extend(["--patchset", str(change_info.patchset)])
[email protected]1227c7d2009-12-22 00:54:27972 file_list = change_info.GetFileNames()
[email protected]fb2b8eb2009-04-23 21:03:42973 else:
[email protected]09c0dba2010-10-14 14:32:22974 file_list = []
[email protected]d3e57542011-03-15 09:43:47975 trychange_args.extend(args)
[email protected]d0891922010-05-31 18:33:16976 return trychange.TryChange(
977 trychange_args,
978 file_list=file_list,
979 swallow_exception=swallow_exception,
980 prog='gcl try',
981 extra_epilog='\n'
982 'When called from gcl, use the format gcl try <change_name>.\n')
[email protected]fb2b8eb2009-04-23 21:03:42983
984
[email protected]62fd6932010-05-27 13:13:23985@need_change_and_args
986@attrs(usage='[--no_presubmit]')
[email protected]4357af22010-05-27 15:42:34987def CMDcommit(change_info, args):
[email protected]62fd6932010-05-27 13:13:23988 """Commits the changelist to the repository."""
[email protected]17f59f22009-06-12 13:27:24989 if not change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42990 print "Nothing to commit, changelist is empty."
[email protected]4c22d722010-05-14 19:01:22991 return 1
[email protected]73ac0f12011-03-17 23:34:22992
993 output = OptionallyDoPresubmitChecks(change_info, True, args)
994 if not output.should_continue():
[email protected]4c22d722010-05-14 19:01:22995 return 1
[email protected]fb2b8eb2009-04-23 21:03:42996
[email protected]1bb04aa2009-06-01 17:52:11997 # We face a problem with svn here: Let's say change 'bleh' modifies
998 # svn:ignore on dir1\. but another unrelated change 'pouet' modifies
999 # dir1\foo.cc. When the user `gcl commit bleh`, foo.cc is *also committed*.
1000 # The only fix is to use --non-recursive but that has its issues too:
1001 # Let's say if dir1 is deleted, --non-recursive must *not* be used otherwise
1002 # you'll get "svn: Cannot non-recursively commit a directory deletion of a
1003 # directory with child nodes". Yay...
1004 commit_cmd = ["svn", "commit"]
[email protected]fb2b8eb2009-04-23 21:03:421005 if change_info.issue:
1006 # Get the latest description from Rietveld.
[email protected]bf1fdca2010-11-01 18:05:361007 change_info.description = change_info.GetIssueDescription()
[email protected]fb2b8eb2009-04-23 21:03:421008
1009 commit_message = change_info.description.replace('\r\n', '\n')
1010 if change_info.issue:
[email protected]bf1fdca2010-11-01 18:05:361011 server = change_info.rietveld
[email protected]fcff9272010-04-29 23:56:191012 if not server.startswith("http://") and not server.startswith("https://"):
1013 server = "http://" + server
1014 commit_message += ('\nReview URL: %s/%d' % (server, change_info.issue))
[email protected]fb2b8eb2009-04-23 21:03:421015
1016 handle, commit_filename = tempfile.mkstemp(text=True)
1017 os.write(handle, commit_message)
1018 os.close(handle)
1019
1020 handle, targets_filename = tempfile.mkstemp(text=True)
[email protected]17f59f22009-06-12 13:27:241021 os.write(handle, "\n".join(change_info.GetFileNames()))
[email protected]fb2b8eb2009-04-23 21:03:421022 os.close(handle)
1023
1024 commit_cmd += ['--file=' + commit_filename]
1025 commit_cmd += ['--targets=' + targets_filename]
1026 # Change the current working directory before calling commit.
1027 previous_cwd = os.getcwd()
[email protected]17f59f22009-06-12 13:27:241028 os.chdir(change_info.GetLocalRoot())
[email protected]fb2b8eb2009-04-23 21:03:421029 output = RunShell(commit_cmd, True)
1030 os.remove(commit_filename)
1031 os.remove(targets_filename)
1032 if output.find("Committed revision") != -1:
1033 change_info.Delete()
1034
1035 if change_info.issue:
1036 revision = re.compile(".*?\nCommitted revision (\d+)",
1037 re.DOTALL).match(output).group(1)
1038 viewvc_url = GetCodeReviewSetting("VIEW_VC")
[email protected]30becc22011-03-17 01:21:221039 change_info.description += '\n'
[email protected]fb2b8eb2009-04-23 21:03:421040 if viewvc_url:
1041 change_info.description += "\nCommitted: " + viewvc_url + revision
1042 change_info.CloseIssue()
1043 os.chdir(previous_cwd)
[email protected]4c22d722010-05-14 19:01:221044 return 0
[email protected]fb2b8eb2009-04-23 21:03:421045
[email protected]2c8d4b22009-06-06 21:03:101046
[email protected]35fe9ad2010-05-25 23:59:541047def CMDchange(args):
[email protected]62fd6932010-05-27 13:13:231048 """Creates or edits a changelist.
1049
1050 Only scans the current directory and subdirectories."""
[email protected]35fe9ad2010-05-25 23:59:541051 if len(args) == 0:
1052 # Generate a random changelist name.
1053 changename = GenerateChangeName()
1054 elif args[0] == '--force':
1055 changename = GenerateChangeName()
1056 else:
1057 changename = args[0]
1058 change_info = ChangeInfo.Load(changename, GetRepositoryRoot(), False, True)
[email protected]9ce98222009-10-19 20:24:171059 silent = FilterFlag(args, "--silent")
[email protected]d36b3ed2009-11-09 18:51:421060
1061 # Verify the user is running the change command from a read-write checkout.
[email protected]5aeb7dd2009-11-17 18:09:011062 svn_info = SVN.CaptureInfo('.')
[email protected]d36b3ed2009-11-09 18:51:421063 if not svn_info:
1064 ErrorExit("Current checkout is unversioned. Please retry with a versioned "
1065 "directory.")
[email protected]d36b3ed2009-11-09 18:51:421066
[email protected]35fe9ad2010-05-25 23:59:541067 if len(args) == 2:
[email protected]c8f3cf82010-09-09 20:00:121068 if not os.path.isfile(args[1]):
1069 ErrorExit('The change "%s" doesn\'t exist.' % args[1])
[email protected]35fe9ad2010-05-25 23:59:541070 f = open(args[1], 'rU')
[email protected]9ce98222009-10-19 20:24:171071 override_description = f.read()
1072 f.close()
1073 else:
1074 override_description = None
[email protected]5aeb7dd2009-11-17 18:09:011075
[email protected]ea452b32009-11-22 20:04:311076 if change_info.issue and not change_info.NeedsUpload():
[email protected]fb2b8eb2009-04-23 21:03:421077 try:
[email protected]bf1fdca2010-11-01 18:05:361078 description = change_info.GetIssueDescription()
[email protected]fb2b8eb2009-04-23 21:03:421079 except urllib2.HTTPError, err:
1080 if err.code == 404:
1081 # The user deleted the issue in Rietveld, so forget the old issue id.
1082 description = change_info.description
[email protected]2c8d4b22009-06-06 21:03:101083 change_info.issue = 0
[email protected]fb2b8eb2009-04-23 21:03:421084 change_info.Save()
1085 else:
1086 ErrorExit("Error getting the description from Rietveld: " + err)
1087 else:
[email protected]85532fc2009-06-04 22:36:531088 if override_description:
1089 description = override_description
1090 else:
1091 description = change_info.description
[email protected]fb2b8eb2009-04-23 21:03:421092
1093 other_files = GetFilesNotInCL()
[email protected]bfd09ce2009-08-05 21:17:231094
[email protected]f0dfba32009-08-07 22:03:371095 # Edited files (as opposed to files with only changed properties) will have
1096 # a letter for the first character in the status string.
[email protected]85532fc2009-06-04 22:36:531097 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE)
[email protected]f0dfba32009-08-07 22:03:371098 affected_files = [x for x in other_files if file_re.match(x[0])]
1099 unaffected_files = [x for x in other_files if not file_re.match(x[0])]
[email protected]fb2b8eb2009-04-23 21:03:421100
[email protected]30becc22011-03-17 01:21:221101 if not change_info.reviewers:
[email protected]624973b2011-03-17 21:17:391102 files_for_review = affected_files[:]
1103 files_for_review.extend(change_info.GetFiles())
1104 suggested_reviewers = suggest_reviewers(change_info, files_for_review)
[email protected]30becc22011-03-17 01:21:221105 if suggested_reviewers:
1106 reviewers_re = re.compile(REVIEWERS_REGEX)
[email protected]624973b2011-03-17 21:17:391107 if not any(reviewers_re.match(l) for l in description.splitlines()):
1108 description += '\n\nR=' + ','.join(suggested_reviewers)
1109
1110 description = description.rstrip() + '\n'
[email protected]30becc22011-03-17 01:21:221111
[email protected]fb2b8eb2009-04-23 21:03:421112 separator1 = ("\n---All lines above this line become the description.\n"
[email protected]17f59f22009-06-12 13:27:241113 "---Repository Root: " + change_info.GetLocalRoot() + "\n"
[email protected]fb2b8eb2009-04-23 21:03:421114 "---Paths in this changelist (" + change_info.name + "):\n")
1115 separator2 = "\n\n---Paths modified but not in any changelist:\n\n"
1116 text = (description + separator1 + '\n' +
[email protected]f0dfba32009-08-07 22:03:371117 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()]))
1118
1119 if change_info.Exists():
1120 text += (separator2 +
1121 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n')
1122 else:
1123 text += ('\n'.join([f[0] + f[1] for f in affected_files]) + '\n' +
1124 separator2)
1125 text += '\n'.join([f[0] + f[1] for f in unaffected_files]) + '\n'
[email protected]fb2b8eb2009-04-23 21:03:421126
1127 handle, filename = tempfile.mkstemp(text=True)
1128 os.write(handle, text)
1129 os.close(handle)
1130
[email protected]d161d842011-03-12 18:56:501131 # Open up the default editor in the system to get the CL description.
[email protected]d161d842011-03-12 18:56:501132 try:
1133 if not silent:
[email protected]7a099612011-03-16 03:26:591134 cmd = '%s %s' % (GetEditor(), filename)
1135 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
1136 # Msysgit requires the usage of 'env' to be present.
1137 cmd = 'env ' + cmd
1138 # shell=True to allow the shell to handle all forms of quotes in $EDITOR.
1139 subprocess.check_call(cmd, shell=True)
[email protected]d161d842011-03-12 18:56:501140 result = gclient_utils.FileRead(filename, 'r')
1141 finally:
1142 os.remove(filename)
[email protected]fb2b8eb2009-04-23 21:03:421143
1144 if not result:
[email protected]4c22d722010-05-14 19:01:221145 return 0
[email protected]fb2b8eb2009-04-23 21:03:421146
1147 split_result = result.split(separator1, 1)
1148 if len(split_result) != 2:
1149 ErrorExit("Don't modify the text starting with ---!\n\n" + result)
1150
[email protected]ea452b32009-11-22 20:04:311151 # Update the CL description if it has changed.
[email protected]fb2b8eb2009-04-23 21:03:421152 new_description = split_result[0]
1153 cl_files_text = split_result[1]
[email protected]85532fc2009-06-04 22:36:531154 if new_description != description or override_description:
[email protected]fb2b8eb2009-04-23 21:03:421155 change_info.description = new_description
[email protected]ea452b32009-11-22 20:04:311156 change_info.needs_upload = True
[email protected]fb2b8eb2009-04-23 21:03:421157
1158 new_cl_files = []
1159 for line in cl_files_text.splitlines():
1160 if not len(line):
1161 continue
1162 if line.startswith("---"):
1163 break
1164 status = line[:7]
[email protected]e3608df2009-11-10 20:22:571165 filename = line[7:]
1166 new_cl_files.append((status, filename))
[email protected]bfd09ce2009-08-05 21:17:231167
[email protected]3a174252010-10-29 15:54:511168 if (not len(change_info.GetFiles()) and not change_info.issue and
1169 not len(new_description) and not new_cl_files):
[email protected]bfd09ce2009-08-05 21:17:231170 ErrorExit("Empty changelist not saved")
1171
[email protected]17f59f22009-06-12 13:27:241172 change_info._files = new_cl_files
[email protected]fb2b8eb2009-04-23 21:03:421173 change_info.Save()
[email protected]53bcf152009-11-13 21:04:101174 if svn_info.get('URL', '').startswith('http:'):
1175 Warn("WARNING: Creating CL in a read-only checkout. You will not be "
1176 "able to commit it!")
1177
[email protected]fb2b8eb2009-04-23 21:03:421178 print change_info.name + " changelist saved."
1179 if change_info.MissingTests():
1180 Warn("WARNING: " + MISSING_TEST_MSG)
1181
[email protected]ea452b32009-11-22 20:04:311182 # Update the Rietveld issue.
1183 if change_info.issue and change_info.NeedsUpload():
1184 change_info.UpdateRietveldDescription()
1185 change_info.needs_upload = False
1186 change_info.Save()
[email protected]4c22d722010-05-14 19:01:221187 return 0
[email protected]ea452b32009-11-22 20:04:311188
1189
[email protected]62fd6932010-05-27 13:13:231190@need_change_and_args
1191def CMDlint(change_info, args):
1192 """Runs cpplint.py on all the files in the change list.
1193
1194 Checks all the files in the changelist for possible style violations.
1195 """
[email protected]fb2b8eb2009-04-23 21:03:421196 try:
1197 import cpplint
1198 except ImportError:
1199 ErrorExit("You need to install cpplint.py to lint C++ files.")
[email protected]fb2b8eb2009-04-23 21:03:421200 # Change the current working directory before calling lint so that it
1201 # shows the correct base.
1202 previous_cwd = os.getcwd()
[email protected]17f59f22009-06-12 13:27:241203 os.chdir(change_info.GetLocalRoot())
[email protected]fb2b8eb2009-04-23 21:03:421204 # Process cpplints arguments if any.
[email protected]17f59f22009-06-12 13:27:241205 filenames = cpplint.ParseArguments(args + change_info.GetFileNames())
[email protected]fb2b8eb2009-04-23 21:03:421206
[email protected]bb816382009-10-29 01:38:021207 white_list = GetCodeReviewSetting("LINT_REGEX")
1208 if not white_list:
[email protected]e72bb632009-10-29 20:15:481209 white_list = DEFAULT_LINT_REGEX
[email protected]bb816382009-10-29 01:38:021210 white_regex = re.compile(white_list)
1211 black_list = GetCodeReviewSetting("LINT_IGNORE_REGEX")
1212 if not black_list:
[email protected]e72bb632009-10-29 20:15:481213 black_list = DEFAULT_LINT_IGNORE_REGEX
[email protected]bb816382009-10-29 01:38:021214 black_regex = re.compile(black_list)
[email protected]b17b55b2010-11-03 14:42:371215 # Access to a protected member _XX of a client class
1216 # pylint: disable=W0212
[email protected]e3608df2009-11-10 20:22:571217 for filename in filenames:
1218 if white_regex.match(filename):
1219 if black_regex.match(filename):
1220 print "Ignoring file %s" % filename
[email protected]fb2b8eb2009-04-23 21:03:421221 else:
[email protected]e3608df2009-11-10 20:22:571222 cpplint.ProcessFile(filename, cpplint._cpplint_state.verbose_level)
[email protected]bb816382009-10-29 01:38:021223 else:
[email protected]e3608df2009-11-10 20:22:571224 print "Skipping file %s" % filename
[email protected]fb2b8eb2009-04-23 21:03:421225
1226 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count
1227 os.chdir(previous_cwd)
[email protected]4c22d722010-05-14 19:01:221228 return 1
[email protected]fb2b8eb2009-04-23 21:03:421229
1230
[email protected]b0dfd352009-06-10 14:12:541231def DoPresubmitChecks(change_info, committing, may_prompt):
[email protected]fb2b8eb2009-04-23 21:03:421232 """Imports presubmit, then calls presubmit.DoPresubmitChecks."""
[email protected]0ff1fab2009-05-22 13:08:151233 root_presubmit = GetCachedFile('PRESUBMIT.py', use_root=True)
[email protected]2e501802009-06-12 22:00:411234 change = presubmit_support.SvnChange(change_info.name,
1235 change_info.description,
1236 change_info.GetLocalRoot(),
1237 change_info.GetFiles(),
1238 change_info.issue,
1239 change_info.patchset)
[email protected]5ac21012011-03-16 02:58:251240 output = presubmit_support.DoPresubmitChecks(change=change,
[email protected]b0dfd352009-06-10 14:12:541241 committing=committing,
[email protected]1033acc2009-05-13 14:36:481242 verbose=False,
1243 output_stream=sys.stdout,
[email protected]0ff1fab2009-05-22 13:08:151244 input_stream=sys.stdin,
[email protected]b0dfd352009-06-10 14:12:541245 default_presubmit=root_presubmit,
[email protected]30becc22011-03-17 01:21:221246 may_prompt=may_prompt,
1247 tbr=False,
1248 host_url=change_info.rietveld)
[email protected]5ac21012011-03-16 02:58:251249 if not output.should_continue() and may_prompt:
1250 # TODO(dpranke): move into DoPresubmitChecks(), unify cmd line args.
[email protected]fb2b8eb2009-04-23 21:03:421251 print "\nPresubmit errors, can't continue (use --no_presubmit to bypass)"
[email protected]5ac21012011-03-16 02:58:251252
[email protected]30becc22011-03-17 01:21:221253 return output
[email protected]fb2b8eb2009-04-23 21:03:421254
1255
[email protected]62fd6932010-05-27 13:13:231256@no_args
1257def CMDchanges():
[email protected]4c22d722010-05-14 19:01:221258 """Lists all the changelists and their files."""
[email protected]fb2b8eb2009-04-23 21:03:421259 for cl in GetCLs():
[email protected]8d5c9a52009-06-12 15:59:081260 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
[email protected]fb2b8eb2009-04-23 21:03:421261 print "\n--- Changelist " + change_info.name + ":"
[email protected]e3608df2009-11-10 20:22:571262 for filename in change_info.GetFiles():
1263 print "".join(filename)
[email protected]4c22d722010-05-14 19:01:221264 return 0
[email protected]fb2b8eb2009-04-23 21:03:421265
1266
[email protected]62fd6932010-05-27 13:13:231267@no_args
1268def CMDdeleteempties():
[email protected]bfd09ce2009-08-05 21:17:231269 """Delete all changelists that have no files."""
1270 print "\n--- Deleting:"
1271 for cl in GetCLs():
1272 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
[email protected]3a174252010-10-29 15:54:511273 if not len(change_info.GetFiles()):
[email protected]bfd09ce2009-08-05 21:17:231274 print change_info.name
1275 change_info.Delete()
[email protected]4c22d722010-05-14 19:01:221276 return 0
1277
1278
[email protected]62fd6932010-05-27 13:13:231279@no_args
1280def CMDnothave():
[email protected]4c22d722010-05-14 19:01:221281 """Lists files unknown to Subversion."""
[email protected]62fd6932010-05-27 13:13:231282 for filename in UnknownFiles():
[email protected]4c22d722010-05-14 19:01:221283 print "? " + "".join(filename)
1284 return 0
1285
1286
[email protected]62fd6932010-05-27 13:13:231287@attrs(usage='<svn options>')
[email protected]35fe9ad2010-05-25 23:59:541288def CMDdiff(args):
[email protected]62fd6932010-05-27 13:13:231289 """Diffs all files in the changelist or all files that aren't in a CL."""
[email protected]35fe9ad2010-05-25 23:59:541290 files = None
1291 if args:
[email protected]62fd6932010-05-27 13:13:231292 change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
[email protected]35fe9ad2010-05-25 23:59:541293 files = change_info.GetFileNames()
1294 else:
[email protected]707c1482010-06-02 19:52:421295 files = [f[1] for f in GetFilesNotInCL()]
[email protected]38729702010-06-01 23:42:031296
1297 root = GetRepositoryRoot()
1298 cmd = ['svn', 'diff']
1299 cmd.extend([os.path.join(root, x) for x in files])
1300 cmd.extend(args)
1301 return RunShellWithReturnCode(cmd, print_output=True)[1]
[email protected]4c22d722010-05-14 19:01:221302
1303
[email protected]62fd6932010-05-27 13:13:231304@no_args
1305def CMDsettings():
1306 """Prints code review settings for this checkout."""
[email protected]4c22d722010-05-14 19:01:221307 # Force load settings
[email protected]6e29d572010-06-04 17:32:201308 GetCodeReviewSetting("UNKNOWN")
[email protected]4c22d722010-05-14 19:01:221309 del CODEREVIEW_SETTINGS['__just_initialized']
1310 print '\n'.join(("%s: %s" % (str(k), str(v))
1311 for (k,v) in CODEREVIEW_SETTINGS.iteritems()))
1312 return 0
1313
1314
[email protected]35fe9ad2010-05-25 23:59:541315@need_change
1316def CMDdescription(change_info):
[email protected]4c22d722010-05-14 19:01:221317 """Prints the description of the specified change to stdout."""
[email protected]4c22d722010-05-14 19:01:221318 print change_info.description
1319 return 0
1320
1321
[email protected]79b7ef02010-11-01 13:25:131322def CMDdelete(args):
[email protected]4c22d722010-05-14 19:01:221323 """Deletes a changelist."""
[email protected]79b7ef02010-11-01 13:25:131324 if not len(args) == 1:
1325 ErrorExit('You need to pass a change list name')
1326 os.remove(GetChangelistInfoFile(args[0]))
[email protected]4c22d722010-05-14 19:01:221327 return 0
1328
1329
[email protected]35fe9ad2010-05-25 23:59:541330def CMDtry(args):
[email protected]62fd6932010-05-27 13:13:231331 """Sends the change to the tryserver to do a test run on your code.
[email protected]4c22d722010-05-14 19:01:221332
1333 To send multiple changes as one path, use a comma-separated list of
1334 changenames. Use 'gcl help try' for more information!"""
1335 # When the change contains no file, send the "changename" positional
1336 # argument to trychange.py.
[email protected]35fe9ad2010-05-25 23:59:541337 # When the command is 'try' and --patchset is used, the patch to try
1338 # is on the Rietveld server.
1339 if not args:
1340 ErrorExit("You need to pass a change list name")
1341 if args[0].find(',') != -1:
1342 change_info = LoadChangelistInfoForMultiple(args[0], GetRepositoryRoot(),
1343 True, True)
1344 else:
1345 change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(),
1346 False, True)
[email protected]4c22d722010-05-14 19:01:221347 if change_info.GetFiles():
[email protected]35fe9ad2010-05-25 23:59:541348 args = args[1:]
[email protected]4c22d722010-05-14 19:01:221349 else:
1350 change_info = None
[email protected]35fe9ad2010-05-25 23:59:541351 return TryChange(change_info, args, swallow_exception=False)
[email protected]4c22d722010-05-14 19:01:221352
1353
[email protected]62fd6932010-05-27 13:13:231354@attrs(usage='<old-name> <new-name>')
[email protected]35fe9ad2010-05-25 23:59:541355def CMDrename(args):
[email protected]4c22d722010-05-14 19:01:221356 """Renames an existing change."""
[email protected]35fe9ad2010-05-25 23:59:541357 if len(args) != 2:
[email protected]4c22d722010-05-14 19:01:221358 ErrorExit("Usage: gcl rename <old-name> <new-name>.")
[email protected]35fe9ad2010-05-25 23:59:541359 src, dst = args
[email protected]4c22d722010-05-14 19:01:221360 src_file = GetChangelistInfoFile(src)
1361 if not os.path.isfile(src_file):
1362 ErrorExit("Change '%s' does not exist." % src)
1363 dst_file = GetChangelistInfoFile(dst)
1364 if os.path.isfile(dst_file):
1365 ErrorExit("Change '%s' already exists; pick a new name." % dst)
1366 os.rename(src_file, dst_file)
1367 print "Change '%s' renamed '%s'." % (src, dst)
1368 return 0
[email protected]bfd09ce2009-08-05 21:17:231369
1370
[email protected]35fe9ad2010-05-25 23:59:541371def CMDpassthru(args):
[email protected]62fd6932010-05-27 13:13:231372 """Everything else that is passed into gcl we redirect to svn.
1373
1374 It assumes a change list name is passed and is converted with the files names.
1375 """
[email protected]35fe9ad2010-05-25 23:59:541376 args = ["svn", args[0]]
1377 if len(args) > 1:
1378 root = GetRepositoryRoot()
1379 change_info = ChangeInfo.Load(args[1], root, True, True)
1380 args.extend([os.path.join(root, x) for x in change_info.GetFileNames()])
1381 return RunShellWithReturnCode(args, print_output=True)[1]
1382
1383
[email protected]62fd6932010-05-27 13:13:231384def Command(name):
1385 return getattr(sys.modules[__name__], 'CMD' + name, None)
1386
1387
1388def GenUsage(command):
1389 """Modify an OptParse object with the function's documentation."""
1390 obj = Command(command)
1391 display = command
1392 more = getattr(obj, 'usage', '')
1393 if command == 'help':
1394 display = '<command>'
[email protected]3a174252010-10-29 15:54:511395 need_change_val = ''
[email protected]62fd6932010-05-27 13:13:231396 if getattr(obj, 'need_change', None):
[email protected]3a174252010-10-29 15:54:511397 need_change_val = ' <change_list>'
[email protected]62fd6932010-05-27 13:13:231398 options = ' [options]'
1399 if getattr(obj, 'no_args', None):
1400 options = ''
[email protected]3a174252010-10-29 15:54:511401 res = 'Usage: gcl %s%s%s %s\n\n' % (display, need_change_val, options, more)
[email protected]62fd6932010-05-27 13:13:231402 res += re.sub('\n ', '\n', obj.__doc__)
1403 return res
1404
1405
1406def CMDhelp(args):
1407 """Prints this help or help for the given command."""
1408 if args and 'CMD' + args[0] in dir(sys.modules[__name__]):
1409 print GenUsage(args[0])
1410
1411 # These commands defer to external tools so give this info too.
1412 if args[0] == 'try':
1413 TryChange(None, ['--help'], swallow_exception=False)
1414 if args[0] == 'upload':
1415 upload.RealMain(['upload.py', '--help'])
1416 return 0
1417
1418 print GenUsage('help')
1419 print sys.modules[__name__].__doc__
1420 print 'version ' + __version__ + '\n'
1421
1422 print('Commands are:\n' + '\n'.join([
1423 ' %-12s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1424 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1425 return 0
1426
1427
[email protected]35fe9ad2010-05-25 23:59:541428def main(argv):
[email protected]c3a15a22010-11-20 03:12:271429 if sys.hexversion < 0x02050000:
1430 print >> sys.stderr, (
1431 '\nYour python version is unsupported, please upgrade.\n')
[email protected]c68f9cb2010-06-17 20:34:181432 if not argv:
1433 argv = ['help']
1434 command = Command(argv[0])
1435 # Help can be run from anywhere.
1436 if command == CMDhelp:
1437 return command(argv[1:])
1438
[email protected]a05be0b2009-06-30 19:13:021439 try:
[email protected]62fd6932010-05-27 13:13:231440 GetRepositoryRoot()
[email protected]5f3eee32009-09-17 00:34:301441 except gclient_utils.Error:
[email protected]58c19382010-09-22 19:53:591442 print >> sys.stderr, 'To use gcl, you need to be in a subversion checkout.'
[email protected]62fd6932010-05-27 13:13:231443 return 1
1444
1445 # Create the directories where we store information about changelists if it
1446 # doesn't exist.
[email protected]807c4462010-07-10 00:45:281447 try:
1448 if not os.path.exists(GetInfoDir()):
1449 os.mkdir(GetInfoDir())
1450 if not os.path.exists(GetChangesDir()):
1451 os.mkdir(GetChangesDir())
1452 if not os.path.exists(GetCacheDir()):
1453 os.mkdir(GetCacheDir())
[email protected]fb2b8eb2009-04-23 21:03:421454
[email protected]807c4462010-07-10 00:45:281455 if command:
1456 return command(argv[1:])
1457 # Unknown command, try to pass that to svn
1458 return CMDpassthru(argv)
1459 except gclient_utils.Error, e:
[email protected]58c19382010-09-22 19:53:591460 print >> sys.stderr, 'Got an exception'
1461 print >> sys.stderr, str(e)
1462 return 1
[email protected]42c7f662010-10-06 23:52:461463 except upload.ClientLoginError, e:
1464 print >> sys.stderr, 'Got an exception logging in to Rietveld'
1465 print >> sys.stderr, str(e)
[email protected]58c19382010-09-22 19:53:591466 except urllib2.HTTPError, e:
1467 if e.code != 500:
1468 raise
1469 print >> sys.stderr, (
1470 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
[email protected]7633e472010-09-22 20:03:141471 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))
[email protected]58c19382010-09-22 19:53:591472 return 1
1473
[email protected]fb2b8eb2009-04-23 21:03:421474
1475if __name__ == "__main__":
[email protected]35fe9ad2010-05-25 23:59:541476 sys.exit(main(sys.argv[1:]))