blob: f936345c16c57e984e7ec205e172efcfe1e462ba [file] [log] [blame]
[email protected]725f1c32011-04-01 20:24:541#!/usr/bin/env python
2# Copyright (c) 2011 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:
[email protected]725f1c32011-04-01 20:24:5428 import json # pylint: disable=F0401
29 except ImportError:
[email protected]fa3843e2010-11-01 13:34:3730 # Import the one included in depot_tools.
31 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party'))
[email protected]d945f362011-03-11 22:52:1932 import simplejson as json # pylint: disable=F0401
[email protected]fa3843e2010-11-01 13:34:3733
[email protected]cb2985f2010-11-03 14:08:3134import breakpad # pylint: disable=W0611
[email protected]ada4c652009-12-03 15:32:0135
[email protected]46a94102009-05-12 20:32:4336# gcl now depends on gclient.
[email protected]5aeb7dd2009-11-17 18:09:0137from scm import SVN
[email protected]30becc22011-03-17 01:21:2238
[email protected]35625c72011-03-23 17:34:0239import fix_encoding
[email protected]5f3eee32009-09-17 00:34:3040import gclient_utils
[email protected]30becc22011-03-17 01:21:2241import presubmit_support
[email protected]c1675e22009-04-27 20:30:4842
[email protected]62fd6932010-05-27 13:13:2343__version__ = '1.2'
[email protected]c1675e22009-04-27 20:30:4844
45
[email protected]fb2b8eb2009-04-23 21:03:4246CODEREVIEW_SETTINGS = {
[email protected]b8260242010-08-19 17:03:1647 # To make gcl send reviews to a server, check in a file named
[email protected]172b6e72010-01-26 00:35:0348 # "codereview.settings" (see |CODEREVIEW_SETTINGS_FILE| below) to your
49 # project's base directory and add the following line to codereview.settings:
50 # CODE_REVIEW_SERVER: codereview.yourserver.org
[email protected]fb2b8eb2009-04-23 21:03:4251}
52
[email protected]fb2b8eb2009-04-23 21:03:4253# globals that store the root of the current repository and the directory where
54# we store information about changelists.
[email protected]98fc2b92009-05-21 14:11:5155REPOSITORY_ROOT = ""
[email protected]fb2b8eb2009-04-23 21:03:4256
57# Filename where we store repository specific information for gcl.
58CODEREVIEW_SETTINGS_FILE = "codereview.settings"
[email protected]b8260242010-08-19 17:03:1659CODEREVIEW_SETTINGS_FILE_NOT_FOUND = (
60 'No %s file found. Please add one.' % CODEREVIEW_SETTINGS_FILE)
[email protected]fb2b8eb2009-04-23 21:03:4261
62# Warning message when the change appears to be missing tests.
63MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!"
64
[email protected]98fc2b92009-05-21 14:11:5165# Global cache of files cached in GetCacheDir().
66FILES_CACHE = {}
[email protected]fb2b8eb2009-04-23 21:03:4267
[email protected]4c22d722010-05-14 19:01:2268# Valid extensions for files we want to lint.
69DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
70DEFAULT_LINT_IGNORE_REGEX = r"$^"
71
[email protected]30becc22011-03-17 01:21:2272REVIEWERS_REGEX = r'\s*R=(.+)'
[email protected]4c22d722010-05-14 19:01:2273
[email protected]e5299012010-04-07 18:02:2674def CheckHomeForFile(filename):
75 """Checks the users home dir for the existence of the given file. Returns
76 the path to the file if it's there, or None if it is not.
77 """
78 home_vars = ['HOME']
79 if sys.platform in ('cygwin', 'win32'):
80 home_vars.append('USERPROFILE')
81 for home_var in home_vars:
82 home = os.getenv(home_var)
83 if home != None:
84 full_path = os.path.join(home, filename)
85 if os.path.exists(full_path):
86 return full_path
87 return None
[email protected]fb2b8eb2009-04-23 21:03:4288
[email protected]35fe9ad2010-05-25 23:59:5489
[email protected]62fd6932010-05-27 13:13:2390def UnknownFiles():
91 """Runs svn status and returns unknown files."""
92 return [item[1] for item in SVN.CaptureStatus([]) if item[0][0] == '?']
[email protected]207fdf32009-04-28 19:57:0193
94
[email protected]fb2b8eb2009-04-23 21:03:4295def GetRepositoryRoot():
96 """Returns the top level directory of the current repository.
97
98 The directory is returned as an absolute path.
99 """
[email protected]98fc2b92009-05-21 14:11:51100 global REPOSITORY_ROOT
101 if not REPOSITORY_ROOT:
[email protected]94b1ee92009-12-19 20:27:20102 REPOSITORY_ROOT = SVN.GetCheckoutRoot(os.getcwd())
103 if not REPOSITORY_ROOT:
[email protected]5f3eee32009-09-17 00:34:30104 raise gclient_utils.Error("gcl run outside of repository")
[email protected]98fc2b92009-05-21 14:11:51105 return REPOSITORY_ROOT
[email protected]fb2b8eb2009-04-23 21:03:42106
107
108def GetInfoDir():
109 """Returns the directory where gcl info files are stored."""
[email protected]374d65e2009-05-21 14:00:52110 return os.path.join(GetRepositoryRoot(), '.svn', 'gcl_info')
111
112
113def GetChangesDir():
114 """Returns the directory where gcl change files are stored."""
115 return os.path.join(GetInfoDir(), 'changes')
[email protected]fb2b8eb2009-04-23 21:03:42116
117
[email protected]98fc2b92009-05-21 14:11:51118def GetCacheDir():
119 """Returns the directory where gcl change files are stored."""
120 return os.path.join(GetInfoDir(), 'cache')
121
122
123def GetCachedFile(filename, max_age=60*60*24*3, use_root=False):
124 """Retrieves a file from the repository and caches it in GetCacheDir() for
125 max_age seconds.
126
127 use_root: If False, look up the arborescence for the first match, otherwise go
128 directory to the root repository.
129
130 Note: The cache will be inconsistent if the same file is retrieved with both
[email protected]bb816382009-10-29 01:38:02131 use_root=True and use_root=False. Don't be stupid.
[email protected]98fc2b92009-05-21 14:11:51132 """
[email protected]98fc2b92009-05-21 14:11:51133 if filename not in FILES_CACHE:
134 # Don't try to look up twice.
135 FILES_CACHE[filename] = None
[email protected]9b613272009-04-24 01:28:28136 # First we check if we have a cached version.
[email protected]a05be0b2009-06-30 19:13:02137 try:
138 cached_file = os.path.join(GetCacheDir(), filename)
[email protected]5f3eee32009-09-17 00:34:30139 except gclient_utils.Error:
[email protected]a05be0b2009-06-30 19:13:02140 return None
[email protected]98fc2b92009-05-21 14:11:51141 if (not os.path.exists(cached_file) or
[email protected]2f6a0d82010-05-12 00:03:30142 (time.time() - os.stat(cached_file).st_mtime) > max_age):
[email protected]b3b494f2010-08-31 20:40:08143 dir_info = SVN.CaptureInfo('.')
144 repo_root = dir_info['Repository Root']
[email protected]98fc2b92009-05-21 14:11:51145 if use_root:
146 url_path = repo_root
147 else:
[email protected]b3b494f2010-08-31 20:40:08148 url_path = dir_info['URL']
[email protected]9b613272009-04-24 01:28:28149 while True:
[email protected]fa44e4a2009-12-03 01:41:13150 # Look in the repository at the current level for the file.
[email protected]b6ee1672010-08-19 17:06:07151 for _ in range(5):
[email protected]b3b494f2010-08-31 20:40:08152 content = None
[email protected]b6ee1672010-08-19 17:06:07153 try:
154 # Take advantage of the fact that svn won't output to stderr in case
155 # of success but will do in case of failure so don't mind putting
156 # stderr into content_array.
157 content_array = []
[email protected]b3b494f2010-08-31 20:40:08158 svn_path = url_path + '/' + filename
[email protected]17368002010-09-01 23:36:32159 args = ['svn', 'cat', svn_path]
[email protected]02913a52010-08-25 20:50:50160 if sys.platform != 'darwin':
161 # MacOSX 10.5.2 has a bug with svn 1.4.4 that will trigger the
162 # 'Can\'t get username or password' and can be fixed easily.
163 # The fix doesn't work if the user upgraded to svn 1.6.x. Bleh.
164 # I don't have time to fix their broken stuff.
165 args.append('--non-interactive')
[email protected]17d01792010-09-01 18:07:10166 gclient_utils.CheckCallAndFilter(
167 args, cwd='.', filter_fn=content_array.append)
[email protected]b6ee1672010-08-19 17:06:07168 # Exit the loop if the file was found. Override content.
169 content = '\n'.join(content_array)
170 break
[email protected]2b9aa8e2010-08-25 20:01:42171 except gclient_utils.Error:
[email protected]b6ee1672010-08-19 17:06:07172 if content_array[0].startswith(
173 'svn: Can\'t get username or password'):
174 ErrorExit('Your svn credentials expired. Please run svn update '
175 'to fix the cached credentials')
[email protected]3c842982010-08-20 17:26:49176 if content_array[0].startswith('svn: Can\'t get password'):
177 ErrorExit('If are using a Mac and svn --version shows 1.4.x, '
178 'please hack gcl.py to remove --non-interactive usage, it\'s'
179 'a bug on your installed copy')
[email protected]dcd15222010-12-21 22:43:19180 if (content_array[0].startswith('svn: File not found:') or
181 content_array[0].endswith('path not found')):
182 break
183 # Otherwise, fall through to trying again.
[email protected]b6ee1672010-08-19 17:06:07184 if content:
[email protected]9b613272009-04-24 01:28:28185 break
[email protected]9b613272009-04-24 01:28:28186 if url_path == repo_root:
187 # Reached the root. Abandoning search.
[email protected]98fc2b92009-05-21 14:11:51188 break
[email protected]9b613272009-04-24 01:28:28189 # Go up one level to try again.
190 url_path = os.path.dirname(url_path)
[email protected]b3b494f2010-08-31 20:40:08191 if content is not None or filename != CODEREVIEW_SETTINGS_FILE:
192 # Write a cached version even if there isn't a file, so we don't try to
193 # fetch it each time. codereview.settings must always be present so do
194 # not cache negative.
195 gclient_utils.FileWrite(cached_file, content or '')
[email protected]98fc2b92009-05-21 14:11:51196 else:
[email protected]0fca4f32009-12-18 15:14:34197 content = gclient_utils.FileRead(cached_file, 'r')
[email protected]98fc2b92009-05-21 14:11:51198 # Keep the content cached in memory.
199 FILES_CACHE[filename] = content
200 return FILES_CACHE[filename]
[email protected]9b613272009-04-24 01:28:28201
[email protected]98fc2b92009-05-21 14:11:51202
203def GetCodeReviewSetting(key):
204 """Returns a value for the given key for this repository."""
205 # Use '__just_initialized' as a flag to determine if the settings were
206 # already initialized.
[email protected]98fc2b92009-05-21 14:11:51207 if '__just_initialized' not in CODEREVIEW_SETTINGS:
[email protected]b0442182009-06-05 14:20:47208 settings_file = GetCachedFile(CODEREVIEW_SETTINGS_FILE)
209 if settings_file:
210 for line in settings_file.splitlines():
[email protected]807c4462010-07-10 00:45:28211 if not line or line.startswith('#'):
[email protected]b0442182009-06-05 14:20:47212 continue
[email protected]807c4462010-07-10 00:45:28213 if not ':' in line:
214 raise gclient_utils.Error(
215 '%s is invalid, please fix. It\'s content:\n\n%s' %
216 (CODEREVIEW_SETTINGS_FILE, settings_file))
[email protected]dd218e52010-08-23 13:02:41217 k, v = line.split(':', 1)
218 CODEREVIEW_SETTINGS[k.strip()] = v.strip()
[email protected]98fc2b92009-05-21 14:11:51219 CODEREVIEW_SETTINGS.setdefault('__just_initialized', None)
[email protected]fb2b8eb2009-04-23 21:03:42220 return CODEREVIEW_SETTINGS.get(key, "")
221
222
[email protected]fb2b8eb2009-04-23 21:03:42223def Warn(msg):
[email protected]6e29d572010-06-04 17:32:20224 print >> sys.stderr, msg
[email protected]223b7192010-06-04 18:52:58225
226
227def ErrorExit(msg):
228 print >> sys.stderr, msg
229 sys.exit(1)
[email protected]fb2b8eb2009-04-23 21:03:42230
231
232def RunShellWithReturnCode(command, print_output=False):
233 """Executes a command and returns the output and the return code."""
[email protected]3a292682010-08-23 18:54:55234 p = gclient_utils.Popen(command, stdout=subprocess.PIPE,
235 stderr=subprocess.STDOUT, universal_newlines=True)
[email protected]fb2b8eb2009-04-23 21:03:42236 if print_output:
237 output_array = []
238 while True:
239 line = p.stdout.readline()
240 if not line:
241 break
242 if print_output:
243 print line.strip('\n')
244 output_array.append(line)
245 output = "".join(output_array)
246 else:
247 output = p.stdout.read()
248 p.wait()
249 p.stdout.close()
250 return output, p.returncode
251
252
253def RunShell(command, print_output=False):
254 """Executes a command and returns the output."""
255 return RunShellWithReturnCode(command, print_output)[0]
256
257
[email protected]51ee0072009-06-08 19:20:05258def FilterFlag(args, flag):
259 """Returns True if the flag is present in args list.
260
261 The flag is removed from args if present.
262 """
263 if flag in args:
264 args.remove(flag)
265 return True
266 return False
267
268
[email protected]be0d1ca2009-05-12 19:23:02269class ChangeInfo(object):
[email protected]fb2b8eb2009-04-23 21:03:42270 """Holds information about a changelist.
271
[email protected]32ba2602009-06-06 18:44:48272 name: change name.
273 issue: the Rietveld issue number or 0 if it hasn't been uploaded yet.
274 patchset: the Rietveld latest patchset number or 0.
[email protected]fb2b8eb2009-04-23 21:03:42275 description: the description.
276 files: a list of 2 tuple containing (status, filename) of changed files,
277 with paths being relative to the top repository directory.
[email protected]8d5c9a52009-06-12 15:59:08278 local_root: Local root directory
[email protected]bf1fdca2010-11-01 18:05:36279 rietveld: rietveld server for this change
[email protected]fb2b8eb2009-04-23 21:03:42280 """
[email protected]bf1fdca2010-11-01 18:05:36281 # Kept for unit test support. This is for the old format, it's deprecated.
[email protected]32ba2602009-06-06 18:44:48282 _SEPARATOR = "\n-----\n"
[email protected]32ba2602009-06-06 18:44:48283
[email protected]ea452b32009-11-22 20:04:31284 def __init__(self, name, issue, patchset, description, files, local_root,
[email protected]bf1fdca2010-11-01 18:05:36285 rietveld, needs_upload=False):
[email protected]fb2b8eb2009-04-23 21:03:42286 self.name = name
[email protected]32ba2602009-06-06 18:44:48287 self.issue = int(issue)
288 self.patchset = int(patchset)
[email protected]20254fc2011-03-22 18:28:59289 self._description = None
290 self._subject = None
291 self._reviewers = None
[email protected]30becc22011-03-17 01:21:22292 self._set_description(description)
[email protected]be0d1ca2009-05-12 19:23:02293 if files is None:
294 files = []
[email protected]17f59f22009-06-12 13:27:24295 self._files = files
[email protected]fb2b8eb2009-04-23 21:03:42296 self.patch = None
[email protected]8d5c9a52009-06-12 15:59:08297 self._local_root = local_root
[email protected]ea452b32009-11-22 20:04:31298 self.needs_upload = needs_upload
[email protected]bf1fdca2010-11-01 18:05:36299 self.rietveld = rietveld
300 if not self.rietveld:
301 # Set the default value.
302 self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER')
[email protected]ea452b32009-11-22 20:04:31303
[email protected]30becc22011-03-17 01:21:22304 def _get_description(self):
[email protected]20254fc2011-03-22 18:28:59305 return self._description
[email protected]30becc22011-03-17 01:21:22306
307 def _set_description(self, description):
[email protected]20254fc2011-03-22 18:28:59308 # TODO(dpranke): Cloned from git_cl.py. These should be shared.
309 if not description:
310 self._description = description
311 return
312
313 parsed_lines = []
314 reviewers_re = re.compile(REVIEWERS_REGEX)
315 reviewers = ''
316 subject = ''
317 for l in description.splitlines():
318 if not subject:
319 subject = l
320 matched_reviewers = reviewers_re.match(l)
321 if matched_reviewers:
322 reviewers = matched_reviewers.group(1).split(',')
323 parsed_lines.append(l)
324
325 if len(subject) > 100:
326 subject = subject[:97] + '...'
327
328 self._subject = subject
329 self._reviewers = reviewers
330 self._description = '\n'.join(parsed_lines)
[email protected]30becc22011-03-17 01:21:22331
332 description = property(_get_description, _set_description)
333
334 @property
335 def reviewers(self):
[email protected]20254fc2011-03-22 18:28:59336 return self._reviewers
[email protected]30becc22011-03-17 01:21:22337
338 @property
339 def subject(self):
[email protected]20254fc2011-03-22 18:28:59340 return self._subject
[email protected]30becc22011-03-17 01:21:22341
[email protected]ea452b32009-11-22 20:04:31342 def NeedsUpload(self):
343 return self.needs_upload
[email protected]fb2b8eb2009-04-23 21:03:42344
[email protected]17f59f22009-06-12 13:27:24345 def GetFileNames(self):
346 """Returns the list of file names included in this change."""
[email protected]e3608df2009-11-10 20:22:57347 return [f[1] for f in self._files]
[email protected]17f59f22009-06-12 13:27:24348
349 def GetFiles(self):
350 """Returns the list of files included in this change with their status."""
351 return self._files
352
353 def GetLocalRoot(self):
354 """Returns the local repository checkout root directory."""
355 return self._local_root
[email protected]fb2b8eb2009-04-23 21:03:42356
[email protected]f0dfba32009-08-07 22:03:37357 def Exists(self):
358 """Returns True if this change already exists (i.e., is not new)."""
359 return (self.issue or self.description or self._files)
360
[email protected]fb2b8eb2009-04-23 21:03:42361 def _NonDeletedFileList(self):
362 """Returns a list of files in this change, not including deleted files."""
[email protected]e3608df2009-11-10 20:22:57363 return [f[1] for f in self.GetFiles()
364 if not f[0].startswith("D")]
[email protected]fb2b8eb2009-04-23 21:03:42365
366 def _AddedFileList(self):
367 """Returns a list of files added in this change."""
[email protected]e3608df2009-11-10 20:22:57368 return [f[1] for f in self.GetFiles() if f[0].startswith("A")]
[email protected]fb2b8eb2009-04-23 21:03:42369
370 def Save(self):
371 """Writes the changelist information to disk."""
[email protected]fa3843e2010-11-01 13:34:37372 data = json.dumps({
373 'issue': self.issue,
374 'patchset': self.patchset,
375 'needs_upload': self.NeedsUpload(),
376 'files': self.GetFiles(),
[email protected]20254fc2011-03-22 18:28:59377 'description': self.description,
[email protected]bf1fdca2010-11-01 18:05:36378 'rietveld': self.rietveld,
[email protected]fa3843e2010-11-01 13:34:37379 }, sort_keys=True, indent=2)
[email protected]fc83c112009-12-18 15:14:10380 gclient_utils.FileWrite(GetChangelistInfoFile(self.name), data)
[email protected]fb2b8eb2009-04-23 21:03:42381
382 def Delete(self):
383 """Removes the changelist information from disk."""
384 os.remove(GetChangelistInfoFile(self.name))
385
386 def CloseIssue(self):
387 """Closes the Rietveld issue for this changelist."""
[email protected]41db6ef2010-11-17 19:42:34388 # Newer versions of Rietveld require us to pass an XSRF token to POST, so
389 # we fetch it from the server.
390 xsrf_token = self.SendToRietveld(
391 '/xsrf_token',
392 extra_headers={'X-Requesting-XSRF-Token': '1'})
393
394 # You cannot close an issue with a GET.
395 # We pass an empty string for the data so it is a POST rather than a GET.
396 data = [("description", self.description),
397 ("xsrf_token", xsrf_token)]
[email protected]fb2b8eb2009-04-23 21:03:42398 ctype, body = upload.EncodeMultipartFormData(data, [])
[email protected]41db6ef2010-11-17 19:42:34399 self.SendToRietveld('/%d/close' % self.issue, payload=body,
400 content_type=ctype)
[email protected]fb2b8eb2009-04-23 21:03:42401
402 def UpdateRietveldDescription(self):
403 """Sets the description for an issue on Rietveld."""
404 data = [("description", self.description),]
405 ctype, body = upload.EncodeMultipartFormData(data, [])
[email protected]41db6ef2010-11-17 19:42:34406 self.SendToRietveld('/%d/description' % self.issue, payload=body,
407 content_type=ctype)
[email protected]bf1fdca2010-11-01 18:05:36408
409 def GetIssueDescription(self):
410 """Returns the issue description from Rietveld."""
411 return self.SendToRietveld('/%d/description' % self.issue)
412
413 def PrimeLint(self):
414 """Do background work on Rietveld to lint the file so that the results are
415 ready when the issue is viewed."""
416 if self.issue and self.patchset:
417 self.SendToRietveld('/lint/issue%s_%s' % (self.issue, self.patchset),
418 timeout=1)
419
[email protected]41db6ef2010-11-17 19:42:34420 def SendToRietveld(self, request_path, timeout=None, **kwargs):
[email protected]bf1fdca2010-11-01 18:05:36421 """Send a POST/GET to Rietveld. Returns the response body."""
422 if not self.rietveld:
423 ErrorExit(CODEREVIEW_SETTINGS_FILE_NOT_FOUND)
424 def GetUserCredentials():
425 """Prompts the user for a username and password."""
426 email = upload.GetEmail('Email (login for uploading to %s)' %
427 self.rietveld)
428 password = getpass.getpass('Password for %s: ' % email)
429 return email, password
430 rpc_server = upload.HttpRpcServer(self.rietveld,
431 GetUserCredentials,
432 save_cookies=True)
433 try:
[email protected]41db6ef2010-11-17 19:42:34434 return rpc_server.Send(request_path, timeout=timeout, **kwargs)
[email protected]bf1fdca2010-11-01 18:05:36435 except urllib2.URLError:
436 if timeout is None:
437 ErrorExit('Error accessing url %s' % request_path)
438 else:
439 return None
[email protected]fb2b8eb2009-04-23 21:03:42440
441 def MissingTests(self):
442 """Returns True if the change looks like it needs unit tests but has none.
443
444 A change needs unit tests if it contains any new source files or methods.
445 """
446 SOURCE_SUFFIXES = [".cc", ".cpp", ".c", ".m", ".mm"]
447 # Ignore third_party entirely.
[email protected]e3608df2009-11-10 20:22:57448 files = [f for f in self._NonDeletedFileList()
449 if f.find("third_party") == -1]
450 added_files = [f for f in self._AddedFileList()
451 if f.find("third_party") == -1]
[email protected]fb2b8eb2009-04-23 21:03:42452
453 # If the change is entirely in third_party, we're done.
454 if len(files) == 0:
455 return False
456
457 # Any new or modified test files?
458 # A test file's name ends with "test.*" or "tests.*".
459 test_files = [test for test in files
460 if os.path.splitext(test)[0].rstrip("s").endswith("test")]
461 if len(test_files) > 0:
462 return False
463
464 # Any new source files?
[email protected]e3608df2009-11-10 20:22:57465 source_files = [item for item in added_files
466 if os.path.splitext(item)[1] in SOURCE_SUFFIXES]
[email protected]fb2b8eb2009-04-23 21:03:42467 if len(source_files) > 0:
468 return True
469
470 # Do the long test, checking the files for new methods.
471 return self._HasNewMethod()
472
473 def _HasNewMethod(self):
474 """Returns True if the changeset contains any new functions, or if a
475 function signature has been changed.
476
477 A function is identified by starting flush left, containing a "(" before
478 the next flush-left line, and either ending with "{" before the next
479 flush-left line or being followed by an unindented "{".
480
481 Currently this returns True for new methods, new static functions, and
482 methods or functions whose signatures have been changed.
483
484 Inline methods added to header files won't be detected by this. That's
485 acceptable for purposes of determining if a unit test is needed, since
486 inline methods should be trivial.
487 """
488 # To check for methods added to source or header files, we need the diffs.
489 # We'll generate them all, since there aren't likely to be many files
490 # apart from source and headers; besides, we'll want them all if we're
491 # uploading anyway.
492 if self.patch is None:
[email protected]17f59f22009-06-12 13:27:24493 self.patch = GenerateDiff(self.GetFileNames())
[email protected]fb2b8eb2009-04-23 21:03:42494
495 definition = ""
496 for line in self.patch.splitlines():
497 if not line.startswith("+"):
498 continue
499 line = line.strip("+").rstrip(" \t")
500 # Skip empty lines, comments, and preprocessor directives.
501 # TODO(pamg): Handle multiline comments if it turns out to be a problem.
502 if line == "" or line.startswith("/") or line.startswith("#"):
503 continue
504
505 # A possible definition ending with "{" is complete, so check it.
506 if definition.endswith("{"):
507 if definition.find("(") != -1:
508 return True
509 definition = ""
510
511 # A { or an indented line, when we're in a definition, continues it.
512 if (definition != "" and
513 (line == "{" or line.startswith(" ") or line.startswith("\t"))):
514 definition += line
515
516 # A flush-left line starts a new possible function definition.
517 elif not line.startswith(" ") and not line.startswith("\t"):
518 definition = line
519
520 return False
521
[email protected]32ba2602009-06-06 18:44:48522 @staticmethod
[email protected]8d5c9a52009-06-12 15:59:08523 def Load(changename, local_root, fail_on_not_found, update_status):
[email protected]32ba2602009-06-06 18:44:48524 """Gets information about a changelist.
[email protected]fb2b8eb2009-04-23 21:03:42525
[email protected]32ba2602009-06-06 18:44:48526 Args:
527 fail_on_not_found: if True, this function will quit the program if the
528 changelist doesn't exist.
529 update_status: if True, the svn status will be updated for all the files
530 and unchanged files will be removed.
531
532 Returns: a ChangeInfo object.
533 """
534 info_file = GetChangelistInfoFile(changename)
535 if not os.path.exists(info_file):
536 if fail_on_not_found:
537 ErrorExit("Changelist " + changename + " not found.")
[email protected]bf1fdca2010-11-01 18:05:36538 return ChangeInfo(changename, 0, 0, '', None, local_root, rietveld=None,
[email protected]ea452b32009-11-22 20:04:31539 needs_upload=False)
[email protected]8a8ea022010-11-01 13:27:32540 content = gclient_utils.FileRead(info_file, 'r')
541 save = False
542 try:
[email protected]fa3843e2010-11-01 13:34:37543 values = ChangeInfo._LoadNewFormat(content)
[email protected]8a8ea022010-11-01 13:27:32544 except ValueError:
[email protected]fa3843e2010-11-01 13:34:37545 try:
546 values = ChangeInfo._LoadOldFormat(content)
547 save = True
548 except ValueError:
549 ErrorExit(
550 ('Changelist file %s is corrupt.\n'
551 'Either run "gcl delete %s" or manually edit the file') % (
552 info_file, changename))
[email protected]8a8ea022010-11-01 13:27:32553 files = values['files']
[email protected]32ba2602009-06-06 18:44:48554 if update_status:
[email protected]79b7ef02010-11-01 13:25:13555 for item in files[:]:
[email protected]e3608df2009-11-10 20:22:57556 filename = os.path.join(local_root, item[1])
[email protected]5aeb7dd2009-11-17 18:09:01557 status_result = SVN.CaptureStatus(filename)
[email protected]32ba2602009-06-06 18:44:48558 if not status_result or not status_result[0][0]:
559 # File has been reverted.
560 save = True
[email protected]e3608df2009-11-10 20:22:57561 files.remove(item)
[email protected]32ba2602009-06-06 18:44:48562 continue
563 status = status_result[0][0]
[email protected]e3608df2009-11-10 20:22:57564 if status != item[0]:
[email protected]32ba2602009-06-06 18:44:48565 save = True
[email protected]e3608df2009-11-10 20:22:57566 files[files.index(item)] = (status, item[1])
[email protected]8a8ea022010-11-01 13:27:32567 change_info = ChangeInfo(changename, values['issue'], values['patchset'],
568 values['description'], files,
[email protected]bf1fdca2010-11-01 18:05:36569 local_root, values.get('rietveld'),
570 values['needs_upload'])
[email protected]32ba2602009-06-06 18:44:48571 if save:
572 change_info.Save()
573 return change_info
[email protected]fb2b8eb2009-04-23 21:03:42574
[email protected]8a8ea022010-11-01 13:27:32575 @staticmethod
576 def _LoadOldFormat(content):
[email protected]bf1fdca2010-11-01 18:05:36577 # The info files have the following format:
578 # issue_id, patchset\n (, patchset is optional)
579 # _SEPARATOR\n
580 # filepath1\n
581 # filepath2\n
582 # .
583 # .
584 # filepathn\n
585 # _SEPARATOR\n
586 # description
[email protected]8a8ea022010-11-01 13:27:32587 split_data = content.split(ChangeInfo._SEPARATOR, 2)
588 if len(split_data) != 3:
589 raise ValueError('Bad change format')
590 values = {
591 'issue': 0,
592 'patchset': 0,
593 'needs_upload': False,
594 'files': [],
595 }
596 items = split_data[0].split(', ')
597 if items[0]:
598 values['issue'] = int(items[0])
599 if len(items) > 1:
600 values['patchset'] = int(items[1])
601 if len(items) > 2:
602 values['needs_upload'] = (items[2] == "dirty")
603 for line in split_data[1].splitlines():
604 status = line[:7]
605 filename = line[7:]
606 values['files'].append((status, filename))
607 values['description'] = split_data[2]
608 return values
609
[email protected]fa3843e2010-11-01 13:34:37610 @staticmethod
611 def _LoadNewFormat(content):
612 return json.loads(content)
613
[email protected]fb2b8eb2009-04-23 21:03:42614
615def GetChangelistInfoFile(changename):
616 """Returns the file that stores information about a changelist."""
617 if not changename or re.search(r'[^\w-]', changename):
618 ErrorExit("Invalid changelist name: " + changename)
[email protected]374d65e2009-05-21 14:00:52619 return os.path.join(GetChangesDir(), changename)
[email protected]fb2b8eb2009-04-23 21:03:42620
621
[email protected]8d5c9a52009-06-12 15:59:08622def LoadChangelistInfoForMultiple(changenames, local_root, fail_on_not_found,
623 update_status):
[email protected]fb2b8eb2009-04-23 21:03:42624 """Loads many changes and merge their files list into one pseudo change.
625
626 This is mainly usefull to concatenate many changes into one for a 'gcl try'.
627 """
628 changes = changenames.split(',')
[email protected]ea452b32009-11-22 20:04:31629 aggregate_change_info = ChangeInfo(changenames, 0, 0, '', None, local_root,
[email protected]bf1fdca2010-11-01 18:05:36630 rietveld=None, needs_upload=False)
[email protected]fb2b8eb2009-04-23 21:03:42631 for change in changes:
[email protected]8d5c9a52009-06-12 15:59:08632 aggregate_change_info._files += ChangeInfo.Load(change,
633 local_root,
634 fail_on_not_found,
[email protected]17f59f22009-06-12 13:27:24635 update_status).GetFiles()
[email protected]fb2b8eb2009-04-23 21:03:42636 return aggregate_change_info
637
638
[email protected]fb2b8eb2009-04-23 21:03:42639def GetCLs():
640 """Returns a list of all the changelists in this repository."""
[email protected]374d65e2009-05-21 14:00:52641 cls = os.listdir(GetChangesDir())
[email protected]fb2b8eb2009-04-23 21:03:42642 if CODEREVIEW_SETTINGS_FILE in cls:
643 cls.remove(CODEREVIEW_SETTINGS_FILE)
644 return cls
645
646
647def GenerateChangeName():
648 """Generate a random changelist name."""
649 random.seed()
650 current_cl_names = GetCLs()
651 while True:
652 cl_name = (random.choice(string.ascii_lowercase) +
653 random.choice(string.digits) +
654 random.choice(string.ascii_lowercase) +
655 random.choice(string.digits))
656 if cl_name not in current_cl_names:
657 return cl_name
658
659
660def GetModifiedFiles():
661 """Returns a set that maps from changelist name to (status,filename) tuples.
662
663 Files not in a changelist have an empty changelist name. Filenames are in
664 relation to the top level directory of the current repository. Note that
665 only the current directory and subdirectories are scanned, in order to
666 improve performance while still being flexible.
667 """
668 files = {}
669
670 # Since the files are normalized to the root folder of the repositary, figure
671 # out what we need to add to the paths.
672 dir_prefix = os.getcwd()[len(GetRepositoryRoot()):].strip(os.sep)
673
674 # Get a list of all files in changelists.
675 files_in_cl = {}
676 for cl in GetCLs():
[email protected]8d5c9a52009-06-12 15:59:08677 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(),
678 fail_on_not_found=True, update_status=False)
[email protected]17f59f22009-06-12 13:27:24679 for status, filename in change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42680 files_in_cl[filename] = change_info.name
681
682 # Get all the modified files.
[email protected]5aeb7dd2009-11-17 18:09:01683 status_result = SVN.CaptureStatus(None)
[email protected]207fdf32009-04-28 19:57:01684 for line in status_result:
685 status = line[0]
686 filename = line[1]
687 if status[0] == "?":
[email protected]fb2b8eb2009-04-23 21:03:42688 continue
[email protected]fb2b8eb2009-04-23 21:03:42689 if dir_prefix:
690 filename = os.path.join(dir_prefix, filename)
691 change_list_name = ""
692 if filename in files_in_cl:
693 change_list_name = files_in_cl[filename]
694 files.setdefault(change_list_name, []).append((status, filename))
695
696 return files
697
698
699def GetFilesNotInCL():
700 """Returns a list of tuples (status,filename) that aren't in any changelists.
701
702 See docstring of GetModifiedFiles for information about path of files and
703 which directories are scanned.
704 """
705 modified_files = GetModifiedFiles()
706 if "" not in modified_files:
707 return []
708 return modified_files[""]
709
710
[email protected]4c22d722010-05-14 19:01:22711def ListFiles(show_unknown_files):
[email protected]fb2b8eb2009-04-23 21:03:42712 files = GetModifiedFiles()
713 cl_keys = files.keys()
714 cl_keys.sort()
715 for cl_name in cl_keys:
[email protected]88c32d82009-10-12 18:24:05716 if not cl_name:
717 continue
718 note = ""
719 change_info = ChangeInfo.Load(cl_name, GetRepositoryRoot(),
720 fail_on_not_found=True, update_status=False)
721 if len(change_info.GetFiles()) != len(files[cl_name]):
722 note = " (Note: this changelist contains files outside this directory)"
723 print "\n--- Changelist " + cl_name + note + ":"
[email protected]e3608df2009-11-10 20:22:57724 for filename in files[cl_name]:
725 print "".join(filename)
[email protected]88c32d82009-10-12 18:24:05726 if show_unknown_files:
[email protected]62fd6932010-05-27 13:13:23727 unknown_files = UnknownFiles()
[email protected]88c32d82009-10-12 18:24:05728 if (files.get('') or (show_unknown_files and len(unknown_files))):
729 print "\n--- Not in any changelist:"
[email protected]e3608df2009-11-10 20:22:57730 for item in files.get('', []):
731 print "".join(item)
[email protected]88c32d82009-10-12 18:24:05732 if show_unknown_files:
[email protected]e3608df2009-11-10 20:22:57733 for filename in unknown_files:
734 print "? %s" % filename
[email protected]4c22d722010-05-14 19:01:22735 return 0
[email protected]fb2b8eb2009-04-23 21:03:42736
737
[email protected]20254fc2011-03-22 18:28:59738def GetEditor():
739 editor = os.environ.get("SVN_EDITOR")
740 if not editor:
741 editor = os.environ.get("EDITOR")
742
743 if not editor:
744 if sys.platform.startswith("win"):
745 editor = "notepad"
746 else:
747 editor = "vi"
748
749 return editor
750
751
[email protected]fb2b8eb2009-04-23 21:03:42752def GenerateDiff(files, root=None):
[email protected]f2f9d552009-12-22 00:12:57753 return SVN.GenerateDiff(files, root=root)
[email protected]fb2b8eb2009-04-23 21:03:42754
[email protected]51ee0072009-06-08 19:20:05755
756def OptionallyDoPresubmitChecks(change_info, committing, args):
757 if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"):
[email protected]30becc22011-03-17 01:21:22758 return presubmit_support.PresubmitOutput()
[email protected]b0dfd352009-06-10 14:12:54759 return DoPresubmitChecks(change_info, committing, True)
[email protected]51ee0072009-06-08 19:20:05760
761
[email protected]62fd6932010-05-27 13:13:23762def defer_attributes(a, b):
763 """Copy attributes from an object (like a function) to another."""
764 for x in dir(a):
765 if not getattr(b, x, None):
766 setattr(b, x, getattr(a, x))
767
768
[email protected]35fe9ad2010-05-25 23:59:54769def need_change(function):
770 """Converts args -> change_info."""
[email protected]3a174252010-10-29 15:54:51771 # pylint: disable=W0612,W0621
[email protected]35fe9ad2010-05-25 23:59:54772 def hook(args):
773 if not len(args) == 1:
774 ErrorExit("You need to pass a change list name")
775 change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(), True, True)
776 return function(change_info)
[email protected]62fd6932010-05-27 13:13:23777 defer_attributes(function, hook)
778 hook.need_change = True
779 hook.no_args = True
[email protected]35fe9ad2010-05-25 23:59:54780 return hook
781
782
[email protected]62fd6932010-05-27 13:13:23783def need_change_and_args(function):
784 """Converts args -> change_info."""
[email protected]3a174252010-10-29 15:54:51785 # pylint: disable=W0612,W0621
[email protected]62fd6932010-05-27 13:13:23786 def hook(args):
[email protected]e56fe822010-05-28 20:36:57787 if not args:
788 ErrorExit("You need to pass a change list name")
[email protected]62fd6932010-05-27 13:13:23789 change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
790 return function(change_info, args)
791 defer_attributes(function, hook)
792 hook.need_change = True
793 return hook
794
795
796def no_args(function):
797 """Make sure no args are passed."""
[email protected]3a174252010-10-29 15:54:51798 # pylint: disable=W0612,W0621
[email protected]62fd6932010-05-27 13:13:23799 def hook(args):
800 if args:
801 ErrorExit("Doesn't support arguments")
802 return function()
803 defer_attributes(function, hook)
804 hook.no_args = True
805 return hook
806
807
808def attrs(**kwargs):
809 """Decorate a function with new attributes."""
810 def decorate(function):
811 for k in kwargs:
812 setattr(function, k, kwargs[k])
813 return function
814 return decorate
815
816
817@no_args
818def CMDopened():
819 """Lists modified files in the current directory down."""
820 return ListFiles(False)
821
822
823@no_args
824def CMDstatus():
825 """Lists modified and unknown files in the current directory down."""
826 return ListFiles(True)
827
828
829@need_change_and_args
[email protected]2b9aa8e2010-08-25 20:01:42830@attrs(usage='[--no_presubmit] [--no_watchlists]')
[email protected]62fd6932010-05-27 13:13:23831def CMDupload(change_info, args):
832 """Uploads the changelist to the server for review.
833
[email protected]5db5ba52010-07-20 15:50:47834 This does not submit a try job; use gcl try to submit a try job.
[email protected]62fd6932010-05-27 13:13:23835 """
[email protected]10ccd112010-08-24 16:48:42836 if '-s' in args or '--server' in args:
837 ErrorExit('Don\'t use the -s flag, fix codereview.settings instead')
[email protected]17f59f22009-06-12 13:27:24838 if not change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42839 print "Nothing to upload, changelist is empty."
[email protected]35fe9ad2010-05-25 23:59:54840 return 0
[email protected]30becc22011-03-17 01:21:22841
842 output = OptionallyDoPresubmitChecks(change_info, False, args)
843 if not output.should_continue():
[email protected]35fe9ad2010-05-25 23:59:54844 return 1
[email protected]62fd6932010-05-27 13:13:23845 no_watchlists = (FilterFlag(args, "--no_watchlists") or
846 FilterFlag(args, "--no-watchlists"))
[email protected]fb2b8eb2009-04-23 21:03:42847
848 # Map --send-mail to --send_mail
[email protected]51ee0072009-06-08 19:20:05849 if FilterFlag(args, "--send-mail"):
[email protected]fb2b8eb2009-04-23 21:03:42850 args.append("--send_mail")
851
[email protected]fb2b8eb2009-04-23 21:03:42852 upload_arg = ["upload.py", "-y"]
[email protected]bf1fdca2010-11-01 18:05:36853 upload_arg.append("--server=%s" % change_info.rietveld)
[email protected]30becc22011-03-17 01:21:22854
855 reviewers = change_info.reviewers or output.reviewers
856 if (reviewers and
857 not any(arg.startswith('-r') or arg.startswith('--reviewer') for
858 arg in args)):
859 upload_arg.append('--reviewers=%s' % ','.join(reviewers))
860
[email protected]fb2b8eb2009-04-23 21:03:42861 upload_arg.extend(args)
862
863 desc_file = ""
864 if change_info.issue: # Uploading a new patchset.
865 found_message = False
866 for arg in args:
867 if arg.startswith("--message") or arg.startswith("-m"):
868 found_message = True
869 break
870
871 if not found_message:
872 upload_arg.append("--message=''")
873
[email protected]32ba2602009-06-06 18:44:48874 upload_arg.append("--issue=%d" % change_info.issue)
[email protected]fb2b8eb2009-04-23 21:03:42875 else: # First time we upload.
876 handle, desc_file = tempfile.mkstemp(text=True)
877 os.write(handle, change_info.description)
878 os.close(handle)
879
[email protected]b2ab4942009-06-11 21:39:19880 # Watchlist processing -- CC people interested in this changeset
881 # http://dev.chromium.org/developers/contributing-code/watchlists
882 if not no_watchlists:
883 import watchlists
[email protected]17f59f22009-06-12 13:27:24884 watchlist = watchlists.Watchlists(change_info.GetLocalRoot())
[email protected]07f01862009-06-12 16:51:08885 watchers = watchlist.GetWatchersForPaths(change_info.GetFileNames())
[email protected]b2ab4942009-06-11 21:39:19886
[email protected]fb2b8eb2009-04-23 21:03:42887 cc_list = GetCodeReviewSetting("CC_LIST")
[email protected]b2ab4942009-06-11 21:39:19888 if not no_watchlists and watchers:
[email protected]6e29d572010-06-04 17:32:20889 # Filter out all empty elements and join by ','
890 cc_list = ','.join(filter(None, [cc_list] + watchers))
[email protected]fb2b8eb2009-04-23 21:03:42891 if cc_list:
892 upload_arg.append("--cc=" + cc_list)
893 upload_arg.append("--description_file=" + desc_file + "")
[email protected]30becc22011-03-17 01:21:22894 if change_info.subject:
[email protected]b68786e2011-03-17 01:39:09895 upload_arg.append("--message=" + change_info.subject)
[email protected]fb2b8eb2009-04-23 21:03:42896
[email protected]83b6e4b2010-03-09 03:16:14897 if GetCodeReviewSetting("PRIVATE") == "True":
898 upload_arg.append("--private")
899
[email protected]fb2b8eb2009-04-23 21:03:42900 # Change the current working directory before calling upload.py so that it
901 # shows the correct base.
902 previous_cwd = os.getcwd()
[email protected]17f59f22009-06-12 13:27:24903 os.chdir(change_info.GetLocalRoot())
[email protected]fb2b8eb2009-04-23 21:03:42904 # If we have a lot of files with long paths, then we won't be able to fit
905 # the command to "svn diff". Instead, we generate the diff manually for
906 # each file and concatenate them before passing it to upload.py.
907 if change_info.patch is None:
[email protected]17f59f22009-06-12 13:27:24908 change_info.patch = GenerateDiff(change_info.GetFileNames())
[email protected]9ce0dff2011-04-04 17:56:50909 try:
910 issue, patchset = upload.RealMain(upload_arg, change_info.patch)
911 except KeyboardInterrupt:
912 sys.exit(1)
[email protected]32ba2602009-06-06 18:44:48913 if issue and patchset:
914 change_info.issue = int(issue)
915 change_info.patchset = int(patchset)
[email protected]fb2b8eb2009-04-23 21:03:42916 change_info.Save()
917
918 if desc_file:
919 os.remove(desc_file)
[email protected]bf1fdca2010-11-01 18:05:36920 change_info.PrimeLint()
[email protected]57e78552009-09-11 23:04:30921 os.chdir(previous_cwd)
[email protected]02287952010-07-23 22:36:58922 print "*** Upload does not submit a try; use gcl try to submit a try. ***"
[email protected]35fe9ad2010-05-25 23:59:54923 return 0
[email protected]fb2b8eb2009-04-23 21:03:42924
[email protected]fb2b8eb2009-04-23 21:03:42925
[email protected]8e13a092010-11-02 19:06:06926@need_change_and_args
927@attrs(usage='[--upload]')
928def CMDpresubmit(change_info, args):
[email protected]62fd6932010-05-27 13:13:23929 """Runs presubmit checks on the change.
930
931 The actual presubmit code is implemented in presubmit_support.py and looks
932 for PRESUBMIT.py files."""
[email protected]17f59f22009-06-12 13:27:24933 if not change_info.GetFiles():
[email protected]8e13a092010-11-02 19:06:06934 print('Nothing to presubmit check, changelist is empty.')
[email protected]4c22d722010-05-14 19:01:22935 return 0
[email protected]8e13a092010-11-02 19:06:06936 parser = optparse.OptionParser()
937 parser.add_option('--upload', action='store_true')
938 options, args = parser.parse_args(args)
939 if args:
940 parser.error('Unrecognized args: %s' % args)
941 if options.upload:
942 print('*** Presubmit checks for UPLOAD would report: ***')
943 return not DoPresubmitChecks(change_info, False, False)
944 else:
945 print('*** Presubmit checks for COMMIT would report: ***')
946 return not DoPresubmitChecks(change_info, True, False)
[email protected]fb2b8eb2009-04-23 21:03:42947
948
949def TryChange(change_info, args, swallow_exception):
950 """Create a diff file of change_info and send it to the try server."""
951 try:
952 import trychange
953 except ImportError:
954 if swallow_exception:
[email protected]35fe9ad2010-05-25 23:59:54955 return 1
[email protected]fb2b8eb2009-04-23 21:03:42956 ErrorExit("You need to install trychange.py to use the try server.")
957
[email protected]18111352009-12-20 17:21:28958 trychange_args = []
[email protected]fb2b8eb2009-04-23 21:03:42959 if change_info:
[email protected]18111352009-12-20 17:21:28960 trychange_args.extend(['--name', change_info.name])
[email protected]32ba2602009-06-06 18:44:48961 if change_info.issue:
962 trychange_args.extend(["--issue", str(change_info.issue)])
963 if change_info.patchset:
964 trychange_args.extend(["--patchset", str(change_info.patchset)])
[email protected]1227c7d2009-12-22 00:54:27965 file_list = change_info.GetFileNames()
[email protected]fb2b8eb2009-04-23 21:03:42966 else:
[email protected]09c0dba2010-10-14 14:32:22967 file_list = []
[email protected]d3e57542011-03-15 09:43:47968 trychange_args.extend(args)
[email protected]d0891922010-05-31 18:33:16969 return trychange.TryChange(
970 trychange_args,
971 file_list=file_list,
972 swallow_exception=swallow_exception,
973 prog='gcl try',
974 extra_epilog='\n'
975 'When called from gcl, use the format gcl try <change_name>.\n')
[email protected]fb2b8eb2009-04-23 21:03:42976
977
[email protected]62fd6932010-05-27 13:13:23978@need_change_and_args
979@attrs(usage='[--no_presubmit]')
[email protected]4357af22010-05-27 15:42:34980def CMDcommit(change_info, args):
[email protected]62fd6932010-05-27 13:13:23981 """Commits the changelist to the repository."""
[email protected]17f59f22009-06-12 13:27:24982 if not change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42983 print "Nothing to commit, changelist is empty."
[email protected]4c22d722010-05-14 19:01:22984 return 1
[email protected]73ac0f12011-03-17 23:34:22985
986 output = OptionallyDoPresubmitChecks(change_info, True, args)
987 if not output.should_continue():
[email protected]4c22d722010-05-14 19:01:22988 return 1
[email protected]fb2b8eb2009-04-23 21:03:42989
[email protected]1bb04aa2009-06-01 17:52:11990 # We face a problem with svn here: Let's say change 'bleh' modifies
991 # svn:ignore on dir1\. but another unrelated change 'pouet' modifies
992 # dir1\foo.cc. When the user `gcl commit bleh`, foo.cc is *also committed*.
993 # The only fix is to use --non-recursive but that has its issues too:
994 # Let's say if dir1 is deleted, --non-recursive must *not* be used otherwise
995 # you'll get "svn: Cannot non-recursively commit a directory deletion of a
996 # directory with child nodes". Yay...
997 commit_cmd = ["svn", "commit"]
[email protected]fb2b8eb2009-04-23 21:03:42998 if change_info.issue:
999 # Get the latest description from Rietveld.
[email protected]bf1fdca2010-11-01 18:05:361000 change_info.description = change_info.GetIssueDescription()
[email protected]fb2b8eb2009-04-23 21:03:421001
1002 commit_message = change_info.description.replace('\r\n', '\n')
1003 if change_info.issue:
[email protected]bf1fdca2010-11-01 18:05:361004 server = change_info.rietveld
[email protected]fcff9272010-04-29 23:56:191005 if not server.startswith("http://") and not server.startswith("https://"):
1006 server = "http://" + server
1007 commit_message += ('\nReview URL: %s/%d' % (server, change_info.issue))
[email protected]fb2b8eb2009-04-23 21:03:421008
1009 handle, commit_filename = tempfile.mkstemp(text=True)
1010 os.write(handle, commit_message)
1011 os.close(handle)
1012
1013 handle, targets_filename = tempfile.mkstemp(text=True)
[email protected]17f59f22009-06-12 13:27:241014 os.write(handle, "\n".join(change_info.GetFileNames()))
[email protected]fb2b8eb2009-04-23 21:03:421015 os.close(handle)
1016
1017 commit_cmd += ['--file=' + commit_filename]
1018 commit_cmd += ['--targets=' + targets_filename]
1019 # Change the current working directory before calling commit.
1020 previous_cwd = os.getcwd()
[email protected]17f59f22009-06-12 13:27:241021 os.chdir(change_info.GetLocalRoot())
[email protected]fb2b8eb2009-04-23 21:03:421022 output = RunShell(commit_cmd, True)
1023 os.remove(commit_filename)
1024 os.remove(targets_filename)
1025 if output.find("Committed revision") != -1:
1026 change_info.Delete()
1027
1028 if change_info.issue:
1029 revision = re.compile(".*?\nCommitted revision (\d+)",
1030 re.DOTALL).match(output).group(1)
1031 viewvc_url = GetCodeReviewSetting("VIEW_VC")
[email protected]30becc22011-03-17 01:21:221032 change_info.description += '\n'
[email protected]fb2b8eb2009-04-23 21:03:421033 if viewvc_url:
1034 change_info.description += "\nCommitted: " + viewvc_url + revision
1035 change_info.CloseIssue()
1036 os.chdir(previous_cwd)
[email protected]4c22d722010-05-14 19:01:221037 return 0
[email protected]fb2b8eb2009-04-23 21:03:421038
[email protected]2c8d4b22009-06-06 21:03:101039
[email protected]35fe9ad2010-05-25 23:59:541040def CMDchange(args):
[email protected]62fd6932010-05-27 13:13:231041 """Creates or edits a changelist.
1042
1043 Only scans the current directory and subdirectories."""
[email protected]35fe9ad2010-05-25 23:59:541044 if len(args) == 0:
1045 # Generate a random changelist name.
1046 changename = GenerateChangeName()
1047 elif args[0] == '--force':
1048 changename = GenerateChangeName()
1049 else:
1050 changename = args[0]
1051 change_info = ChangeInfo.Load(changename, GetRepositoryRoot(), False, True)
[email protected]9ce98222009-10-19 20:24:171052 silent = FilterFlag(args, "--silent")
[email protected]d36b3ed2009-11-09 18:51:421053
1054 # Verify the user is running the change command from a read-write checkout.
[email protected]5aeb7dd2009-11-17 18:09:011055 svn_info = SVN.CaptureInfo('.')
[email protected]d36b3ed2009-11-09 18:51:421056 if not svn_info:
1057 ErrorExit("Current checkout is unversioned. Please retry with a versioned "
1058 "directory.")
[email protected]d36b3ed2009-11-09 18:51:421059
[email protected]35fe9ad2010-05-25 23:59:541060 if len(args) == 2:
[email protected]c8f3cf82010-09-09 20:00:121061 if not os.path.isfile(args[1]):
1062 ErrorExit('The change "%s" doesn\'t exist.' % args[1])
[email protected]35fe9ad2010-05-25 23:59:541063 f = open(args[1], 'rU')
[email protected]9ce98222009-10-19 20:24:171064 override_description = f.read()
1065 f.close()
1066 else:
1067 override_description = None
[email protected]5aeb7dd2009-11-17 18:09:011068
[email protected]ea452b32009-11-22 20:04:311069 if change_info.issue and not change_info.NeedsUpload():
[email protected]fb2b8eb2009-04-23 21:03:421070 try:
[email protected]bf1fdca2010-11-01 18:05:361071 description = change_info.GetIssueDescription()
[email protected]fb2b8eb2009-04-23 21:03:421072 except urllib2.HTTPError, err:
1073 if err.code == 404:
1074 # The user deleted the issue in Rietveld, so forget the old issue id.
1075 description = change_info.description
[email protected]2c8d4b22009-06-06 21:03:101076 change_info.issue = 0
[email protected]fb2b8eb2009-04-23 21:03:421077 change_info.Save()
1078 else:
1079 ErrorExit("Error getting the description from Rietveld: " + err)
1080 else:
[email protected]85532fc2009-06-04 22:36:531081 if override_description:
1082 description = override_description
1083 else:
1084 description = change_info.description
[email protected]fb2b8eb2009-04-23 21:03:421085
1086 other_files = GetFilesNotInCL()
[email protected]bfd09ce2009-08-05 21:17:231087
[email protected]f0dfba32009-08-07 22:03:371088 # Edited files (as opposed to files with only changed properties) will have
1089 # a letter for the first character in the status string.
[email protected]85532fc2009-06-04 22:36:531090 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE)
[email protected]f0dfba32009-08-07 22:03:371091 affected_files = [x for x in other_files if file_re.match(x[0])]
1092 unaffected_files = [x for x in other_files if not file_re.match(x[0])]
[email protected]fb2b8eb2009-04-23 21:03:421093
[email protected]20254fc2011-03-22 18:28:591094 description = description.rstrip() + '\n'
[email protected]30becc22011-03-17 01:21:221095
[email protected]fb2b8eb2009-04-23 21:03:421096 separator1 = ("\n---All lines above this line become the description.\n"
[email protected]17f59f22009-06-12 13:27:241097 "---Repository Root: " + change_info.GetLocalRoot() + "\n"
[email protected]fb2b8eb2009-04-23 21:03:421098 "---Paths in this changelist (" + change_info.name + "):\n")
1099 separator2 = "\n\n---Paths modified but not in any changelist:\n\n"
[email protected]20254fc2011-03-22 18:28:591100 text = (description + separator1 + '\n' +
1101 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()]))
[email protected]f0dfba32009-08-07 22:03:371102
1103 if change_info.Exists():
[email protected]20254fc2011-03-22 18:28:591104 text += (separator2 +
1105 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n')
[email protected]f0dfba32009-08-07 22:03:371106 else:
[email protected]20254fc2011-03-22 18:28:591107 text += ('\n'.join([f[0] + f[1] for f in affected_files]) + '\n' +
1108 separator2)
1109 text += '\n'.join([f[0] + f[1] for f in unaffected_files]) + '\n'
[email protected]fb2b8eb2009-04-23 21:03:421110
[email protected]20254fc2011-03-22 18:28:591111 handle, filename = tempfile.mkstemp(text=True)
1112 os.write(handle, text)
1113 os.close(handle)
[email protected]fb2b8eb2009-04-23 21:03:421114
[email protected]20254fc2011-03-22 18:28:591115 # Open up the default editor in the system to get the CL description.
1116 try:
1117 if not silent:
1118 cmd = '%s %s' % (GetEditor(), filename)
1119 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
1120 # Msysgit requires the usage of 'env' to be present.
1121 cmd = 'env ' + cmd
1122 # shell=True to allow the shell to handle all forms of quotes in $EDITOR.
1123 subprocess.check_call(cmd, shell=True)
1124 result = gclient_utils.FileRead(filename, 'r')
1125 finally:
1126 os.remove(filename)
[email protected]fb2b8eb2009-04-23 21:03:421127
1128 if not result:
[email protected]4c22d722010-05-14 19:01:221129 return 0
[email protected]fb2b8eb2009-04-23 21:03:421130
1131 split_result = result.split(separator1, 1)
1132 if len(split_result) != 2:
1133 ErrorExit("Don't modify the text starting with ---!\n\n" + result)
1134
[email protected]ea452b32009-11-22 20:04:311135 # Update the CL description if it has changed.
[email protected]fb2b8eb2009-04-23 21:03:421136 new_description = split_result[0]
1137 cl_files_text = split_result[1]
[email protected]20254fc2011-03-22 18:28:591138 if new_description != description or override_description:
1139 change_info.description = new_description
[email protected]ea452b32009-11-22 20:04:311140 change_info.needs_upload = True
[email protected]fb2b8eb2009-04-23 21:03:421141
1142 new_cl_files = []
1143 for line in cl_files_text.splitlines():
1144 if not len(line):
1145 continue
1146 if line.startswith("---"):
1147 break
1148 status = line[:7]
[email protected]e3608df2009-11-10 20:22:571149 filename = line[7:]
1150 new_cl_files.append((status, filename))
[email protected]bfd09ce2009-08-05 21:17:231151
[email protected]3a174252010-10-29 15:54:511152 if (not len(change_info.GetFiles()) and not change_info.issue and
[email protected]20254fc2011-03-22 18:28:591153 not len(new_description) and not new_cl_files):
[email protected]bfd09ce2009-08-05 21:17:231154 ErrorExit("Empty changelist not saved")
1155
[email protected]17f59f22009-06-12 13:27:241156 change_info._files = new_cl_files
[email protected]fb2b8eb2009-04-23 21:03:421157 change_info.Save()
[email protected]53bcf152009-11-13 21:04:101158 if svn_info.get('URL', '').startswith('http:'):
1159 Warn("WARNING: Creating CL in a read-only checkout. You will not be "
1160 "able to commit it!")
1161
[email protected]fb2b8eb2009-04-23 21:03:421162 print change_info.name + " changelist saved."
1163 if change_info.MissingTests():
1164 Warn("WARNING: " + MISSING_TEST_MSG)
1165
[email protected]ea452b32009-11-22 20:04:311166 # Update the Rietveld issue.
1167 if change_info.issue and change_info.NeedsUpload():
1168 change_info.UpdateRietveldDescription()
1169 change_info.needs_upload = False
1170 change_info.Save()
[email protected]4c22d722010-05-14 19:01:221171 return 0
[email protected]ea452b32009-11-22 20:04:311172
1173
[email protected]62fd6932010-05-27 13:13:231174@need_change_and_args
1175def CMDlint(change_info, args):
1176 """Runs cpplint.py on all the files in the change list.
1177
1178 Checks all the files in the changelist for possible style violations.
1179 """
[email protected]fb2b8eb2009-04-23 21:03:421180 try:
1181 import cpplint
1182 except ImportError:
1183 ErrorExit("You need to install cpplint.py to lint C++ files.")
[email protected]fb2b8eb2009-04-23 21:03:421184 # Change the current working directory before calling lint so that it
1185 # shows the correct base.
1186 previous_cwd = os.getcwd()
[email protected]17f59f22009-06-12 13:27:241187 os.chdir(change_info.GetLocalRoot())
[email protected]fb2b8eb2009-04-23 21:03:421188 # Process cpplints arguments if any.
[email protected]17f59f22009-06-12 13:27:241189 filenames = cpplint.ParseArguments(args + change_info.GetFileNames())
[email protected]fb2b8eb2009-04-23 21:03:421190
[email protected]bb816382009-10-29 01:38:021191 white_list = GetCodeReviewSetting("LINT_REGEX")
1192 if not white_list:
[email protected]e72bb632009-10-29 20:15:481193 white_list = DEFAULT_LINT_REGEX
[email protected]bb816382009-10-29 01:38:021194 white_regex = re.compile(white_list)
1195 black_list = GetCodeReviewSetting("LINT_IGNORE_REGEX")
1196 if not black_list:
[email protected]e72bb632009-10-29 20:15:481197 black_list = DEFAULT_LINT_IGNORE_REGEX
[email protected]bb816382009-10-29 01:38:021198 black_regex = re.compile(black_list)
[email protected]b17b55b2010-11-03 14:42:371199 # Access to a protected member _XX of a client class
1200 # pylint: disable=W0212
[email protected]e3608df2009-11-10 20:22:571201 for filename in filenames:
1202 if white_regex.match(filename):
1203 if black_regex.match(filename):
1204 print "Ignoring file %s" % filename
[email protected]fb2b8eb2009-04-23 21:03:421205 else:
[email protected]e3608df2009-11-10 20:22:571206 cpplint.ProcessFile(filename, cpplint._cpplint_state.verbose_level)
[email protected]bb816382009-10-29 01:38:021207 else:
[email protected]e3608df2009-11-10 20:22:571208 print "Skipping file %s" % filename
[email protected]fb2b8eb2009-04-23 21:03:421209
1210 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count
1211 os.chdir(previous_cwd)
[email protected]4c22d722010-05-14 19:01:221212 return 1
[email protected]fb2b8eb2009-04-23 21:03:421213
1214
[email protected]b0dfd352009-06-10 14:12:541215def DoPresubmitChecks(change_info, committing, may_prompt):
[email protected]fb2b8eb2009-04-23 21:03:421216 """Imports presubmit, then calls presubmit.DoPresubmitChecks."""
[email protected]0ff1fab2009-05-22 13:08:151217 root_presubmit = GetCachedFile('PRESUBMIT.py', use_root=True)
[email protected]2e501802009-06-12 22:00:411218 change = presubmit_support.SvnChange(change_info.name,
1219 change_info.description,
1220 change_info.GetLocalRoot(),
1221 change_info.GetFiles(),
1222 change_info.issue,
1223 change_info.patchset)
[email protected]5ac21012011-03-16 02:58:251224 output = presubmit_support.DoPresubmitChecks(change=change,
[email protected]b0dfd352009-06-10 14:12:541225 committing=committing,
[email protected]1033acc2009-05-13 14:36:481226 verbose=False,
1227 output_stream=sys.stdout,
[email protected]0ff1fab2009-05-22 13:08:151228 input_stream=sys.stdin,
[email protected]b0dfd352009-06-10 14:12:541229 default_presubmit=root_presubmit,
[email protected]30becc22011-03-17 01:21:221230 may_prompt=may_prompt,
1231 tbr=False,
1232 host_url=change_info.rietveld)
[email protected]5ac21012011-03-16 02:58:251233 if not output.should_continue() and may_prompt:
1234 # TODO(dpranke): move into DoPresubmitChecks(), unify cmd line args.
[email protected]fb2b8eb2009-04-23 21:03:421235 print "\nPresubmit errors, can't continue (use --no_presubmit to bypass)"
[email protected]5ac21012011-03-16 02:58:251236
[email protected]30becc22011-03-17 01:21:221237 return output
[email protected]fb2b8eb2009-04-23 21:03:421238
1239
[email protected]62fd6932010-05-27 13:13:231240@no_args
1241def CMDchanges():
[email protected]4c22d722010-05-14 19:01:221242 """Lists all the changelists and their files."""
[email protected]fb2b8eb2009-04-23 21:03:421243 for cl in GetCLs():
[email protected]8d5c9a52009-06-12 15:59:081244 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
[email protected]fb2b8eb2009-04-23 21:03:421245 print "\n--- Changelist " + change_info.name + ":"
[email protected]e3608df2009-11-10 20:22:571246 for filename in change_info.GetFiles():
1247 print "".join(filename)
[email protected]4c22d722010-05-14 19:01:221248 return 0
[email protected]fb2b8eb2009-04-23 21:03:421249
1250
[email protected]62fd6932010-05-27 13:13:231251@no_args
1252def CMDdeleteempties():
[email protected]bfd09ce2009-08-05 21:17:231253 """Delete all changelists that have no files."""
1254 print "\n--- Deleting:"
1255 for cl in GetCLs():
1256 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
[email protected]3a174252010-10-29 15:54:511257 if not len(change_info.GetFiles()):
[email protected]bfd09ce2009-08-05 21:17:231258 print change_info.name
1259 change_info.Delete()
[email protected]4c22d722010-05-14 19:01:221260 return 0
1261
1262
[email protected]62fd6932010-05-27 13:13:231263@no_args
1264def CMDnothave():
[email protected]4c22d722010-05-14 19:01:221265 """Lists files unknown to Subversion."""
[email protected]62fd6932010-05-27 13:13:231266 for filename in UnknownFiles():
[email protected]4c22d722010-05-14 19:01:221267 print "? " + "".join(filename)
1268 return 0
1269
1270
[email protected]62fd6932010-05-27 13:13:231271@attrs(usage='<svn options>')
[email protected]35fe9ad2010-05-25 23:59:541272def CMDdiff(args):
[email protected]62fd6932010-05-27 13:13:231273 """Diffs all files in the changelist or all files that aren't in a CL."""
[email protected]35fe9ad2010-05-25 23:59:541274 files = None
1275 if args:
[email protected]62fd6932010-05-27 13:13:231276 change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
[email protected]35fe9ad2010-05-25 23:59:541277 files = change_info.GetFileNames()
1278 else:
[email protected]707c1482010-06-02 19:52:421279 files = [f[1] for f in GetFilesNotInCL()]
[email protected]38729702010-06-01 23:42:031280
1281 root = GetRepositoryRoot()
1282 cmd = ['svn', 'diff']
1283 cmd.extend([os.path.join(root, x) for x in files])
1284 cmd.extend(args)
1285 return RunShellWithReturnCode(cmd, print_output=True)[1]
[email protected]4c22d722010-05-14 19:01:221286
1287
[email protected]62fd6932010-05-27 13:13:231288@no_args
1289def CMDsettings():
1290 """Prints code review settings for this checkout."""
[email protected]4c22d722010-05-14 19:01:221291 # Force load settings
[email protected]6e29d572010-06-04 17:32:201292 GetCodeReviewSetting("UNKNOWN")
[email protected]4c22d722010-05-14 19:01:221293 del CODEREVIEW_SETTINGS['__just_initialized']
1294 print '\n'.join(("%s: %s" % (str(k), str(v))
1295 for (k,v) in CODEREVIEW_SETTINGS.iteritems()))
1296 return 0
1297
1298
[email protected]35fe9ad2010-05-25 23:59:541299@need_change
1300def CMDdescription(change_info):
[email protected]4c22d722010-05-14 19:01:221301 """Prints the description of the specified change to stdout."""
[email protected]4c22d722010-05-14 19:01:221302 print change_info.description
1303 return 0
1304
1305
[email protected]79b7ef02010-11-01 13:25:131306def CMDdelete(args):
[email protected]4c22d722010-05-14 19:01:221307 """Deletes a changelist."""
[email protected]79b7ef02010-11-01 13:25:131308 if not len(args) == 1:
1309 ErrorExit('You need to pass a change list name')
[email protected]a2d7edf2011-04-04 17:53:451310 filepath = GetChangelistInfoFile(args[0])
1311 if not os.path.isfile(filepath):
1312 ErrorExit('You need to pass a valid change list name')
1313 os.remove(filepath)
[email protected]4c22d722010-05-14 19:01:221314 return 0
1315
1316
[email protected]35fe9ad2010-05-25 23:59:541317def CMDtry(args):
[email protected]62fd6932010-05-27 13:13:231318 """Sends the change to the tryserver to do a test run on your code.
[email protected]4c22d722010-05-14 19:01:221319
1320 To send multiple changes as one path, use a comma-separated list of
1321 changenames. Use 'gcl help try' for more information!"""
1322 # When the change contains no file, send the "changename" positional
1323 # argument to trychange.py.
[email protected]35fe9ad2010-05-25 23:59:541324 # When the command is 'try' and --patchset is used, the patch to try
1325 # is on the Rietveld server.
1326 if not args:
1327 ErrorExit("You need to pass a change list name")
1328 if args[0].find(',') != -1:
1329 change_info = LoadChangelistInfoForMultiple(args[0], GetRepositoryRoot(),
1330 True, True)
1331 else:
1332 change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(),
1333 False, True)
[email protected]4c22d722010-05-14 19:01:221334 if change_info.GetFiles():
[email protected]35fe9ad2010-05-25 23:59:541335 args = args[1:]
[email protected]4c22d722010-05-14 19:01:221336 else:
1337 change_info = None
[email protected]35fe9ad2010-05-25 23:59:541338 return TryChange(change_info, args, swallow_exception=False)
[email protected]4c22d722010-05-14 19:01:221339
1340
[email protected]62fd6932010-05-27 13:13:231341@attrs(usage='<old-name> <new-name>')
[email protected]35fe9ad2010-05-25 23:59:541342def CMDrename(args):
[email protected]4c22d722010-05-14 19:01:221343 """Renames an existing change."""
[email protected]35fe9ad2010-05-25 23:59:541344 if len(args) != 2:
[email protected]4c22d722010-05-14 19:01:221345 ErrorExit("Usage: gcl rename <old-name> <new-name>.")
[email protected]35fe9ad2010-05-25 23:59:541346 src, dst = args
[email protected]4c22d722010-05-14 19:01:221347 src_file = GetChangelistInfoFile(src)
1348 if not os.path.isfile(src_file):
1349 ErrorExit("Change '%s' does not exist." % src)
1350 dst_file = GetChangelistInfoFile(dst)
1351 if os.path.isfile(dst_file):
1352 ErrorExit("Change '%s' already exists; pick a new name." % dst)
1353 os.rename(src_file, dst_file)
1354 print "Change '%s' renamed '%s'." % (src, dst)
1355 return 0
[email protected]bfd09ce2009-08-05 21:17:231356
1357
[email protected]35fe9ad2010-05-25 23:59:541358def CMDpassthru(args):
[email protected]62fd6932010-05-27 13:13:231359 """Everything else that is passed into gcl we redirect to svn.
1360
1361 It assumes a change list name is passed and is converted with the files names.
1362 """
[email protected]35fe9ad2010-05-25 23:59:541363 args = ["svn", args[0]]
1364 if len(args) > 1:
1365 root = GetRepositoryRoot()
1366 change_info = ChangeInfo.Load(args[1], root, True, True)
1367 args.extend([os.path.join(root, x) for x in change_info.GetFileNames()])
1368 return RunShellWithReturnCode(args, print_output=True)[1]
1369
1370
[email protected]62fd6932010-05-27 13:13:231371def Command(name):
1372 return getattr(sys.modules[__name__], 'CMD' + name, None)
1373
1374
1375def GenUsage(command):
1376 """Modify an OptParse object with the function's documentation."""
1377 obj = Command(command)
1378 display = command
1379 more = getattr(obj, 'usage', '')
1380 if command == 'help':
1381 display = '<command>'
[email protected]3a174252010-10-29 15:54:511382 need_change_val = ''
[email protected]62fd6932010-05-27 13:13:231383 if getattr(obj, 'need_change', None):
[email protected]3a174252010-10-29 15:54:511384 need_change_val = ' <change_list>'
[email protected]62fd6932010-05-27 13:13:231385 options = ' [options]'
1386 if getattr(obj, 'no_args', None):
1387 options = ''
[email protected]3a174252010-10-29 15:54:511388 res = 'Usage: gcl %s%s%s %s\n\n' % (display, need_change_val, options, more)
[email protected]62fd6932010-05-27 13:13:231389 res += re.sub('\n ', '\n', obj.__doc__)
1390 return res
1391
1392
1393def CMDhelp(args):
1394 """Prints this help or help for the given command."""
1395 if args and 'CMD' + args[0] in dir(sys.modules[__name__]):
1396 print GenUsage(args[0])
1397
1398 # These commands defer to external tools so give this info too.
1399 if args[0] == 'try':
1400 TryChange(None, ['--help'], swallow_exception=False)
1401 if args[0] == 'upload':
1402 upload.RealMain(['upload.py', '--help'])
1403 return 0
1404
1405 print GenUsage('help')
1406 print sys.modules[__name__].__doc__
1407 print 'version ' + __version__ + '\n'
1408
1409 print('Commands are:\n' + '\n'.join([
1410 ' %-12s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1411 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1412 return 0
1413
1414
[email protected]35fe9ad2010-05-25 23:59:541415def main(argv):
[email protected]c3a15a22010-11-20 03:12:271416 if sys.hexversion < 0x02050000:
1417 print >> sys.stderr, (
1418 '\nYour python version is unsupported, please upgrade.\n')
[email protected]c68f9cb2010-06-17 20:34:181419 if not argv:
1420 argv = ['help']
1421 command = Command(argv[0])
1422 # Help can be run from anywhere.
1423 if command == CMDhelp:
1424 return command(argv[1:])
1425
[email protected]a05be0b2009-06-30 19:13:021426 try:
[email protected]62fd6932010-05-27 13:13:231427 GetRepositoryRoot()
[email protected]5f3eee32009-09-17 00:34:301428 except gclient_utils.Error:
[email protected]58c19382010-09-22 19:53:591429 print >> sys.stderr, 'To use gcl, you need to be in a subversion checkout.'
[email protected]62fd6932010-05-27 13:13:231430 return 1
1431
1432 # Create the directories where we store information about changelists if it
1433 # doesn't exist.
[email protected]807c4462010-07-10 00:45:281434 try:
1435 if not os.path.exists(GetInfoDir()):
1436 os.mkdir(GetInfoDir())
1437 if not os.path.exists(GetChangesDir()):
1438 os.mkdir(GetChangesDir())
1439 if not os.path.exists(GetCacheDir()):
1440 os.mkdir(GetCacheDir())
[email protected]fb2b8eb2009-04-23 21:03:421441
[email protected]807c4462010-07-10 00:45:281442 if command:
1443 return command(argv[1:])
1444 # Unknown command, try to pass that to svn
1445 return CMDpassthru(argv)
1446 except gclient_utils.Error, e:
[email protected]58c19382010-09-22 19:53:591447 print >> sys.stderr, 'Got an exception'
1448 print >> sys.stderr, str(e)
1449 return 1
[email protected]42c7f662010-10-06 23:52:461450 except upload.ClientLoginError, e:
1451 print >> sys.stderr, 'Got an exception logging in to Rietveld'
1452 print >> sys.stderr, str(e)
[email protected]58c19382010-09-22 19:53:591453 except urllib2.HTTPError, e:
1454 if e.code != 500:
1455 raise
1456 print >> sys.stderr, (
1457 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
[email protected]7633e472010-09-22 20:03:141458 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))
[email protected]58c19382010-09-22 19:53:591459 return 1
1460
[email protected]fb2b8eb2009-04-23 21:03:421461
1462if __name__ == "__main__":
[email protected]35625c72011-03-23 17:34:021463 fix_encoding.fix_encoding()
[email protected]35fe9ad2010-05-25 23:59:541464 sys.exit(main(sys.argv[1:]))