blob: f6de1f7e92510e7e109c6a87d66db5dea5c22f9e [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
[email protected]8e13a092010-11-02 19:06:0611import optparse
[email protected]fb2b8eb2009-04-23 21:03:4212import os
13import random
14import re
15import string
[email protected]fb2b8eb2009-04-23 21:03:4216import sys
17import tempfile
[email protected]2f6a0d82010-05-12 00:03:3018import time
[email protected]fb2b8eb2009-04-23 21:03:4219import urllib2
20
[email protected]e13e12a2011-09-08 17:10:1121import breakpad # pylint: disable=W0611
22
[email protected]fa3843e2010-11-01 13:34:3723try:
[email protected]d945f362011-03-11 22:52:1924 import simplejson as json # pylint: disable=F0401
[email protected]fa3843e2010-11-01 13:34:3725except ImportError:
26 try:
[email protected]725f1c32011-04-01 20:24:5427 import json # pylint: disable=F0401
28 except ImportError:
[email protected]fa3843e2010-11-01 13:34:3729 # Import the one included in depot_tools.
30 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party'))
[email protected]d945f362011-03-11 22:52:1931 import simplejson as json # pylint: disable=F0401
[email protected]fa3843e2010-11-01 13:34:3732
[email protected]35625c72011-03-23 17:34:0233import fix_encoding
[email protected]5f3eee32009-09-17 00:34:3034import gclient_utils
[email protected]30becc22011-03-17 01:21:2235import presubmit_support
[email protected]cab38e92011-04-09 00:30:5136import rietveld
[email protected]e13e12a2011-09-08 17:10:1137from scm import SVN
[email protected]31cb48a2011-04-04 18:01:3638import subprocess2
[email protected]e13e12a2011-09-08 17:10:1139from third_party import upload
[email protected]c1675e22009-04-27 20:30:4840
[email protected]97335082011-10-20 15:00:1641__version__ = '1.2.1'
[email protected]c1675e22009-04-27 20:30:4842
43
[email protected]fb2b8eb2009-04-23 21:03:4244CODEREVIEW_SETTINGS = {
[email protected]b8260242010-08-19 17:03:1645 # To make gcl send reviews to a server, check in a file named
[email protected]172b6e72010-01-26 00:35:0346 # "codereview.settings" (see |CODEREVIEW_SETTINGS_FILE| below) to your
47 # project's base directory and add the following line to codereview.settings:
48 # CODE_REVIEW_SERVER: codereview.yourserver.org
[email protected]fb2b8eb2009-04-23 21:03:4249}
50
[email protected]fb2b8eb2009-04-23 21:03:4251# globals that store the root of the current repository and the directory where
52# we store information about changelists.
[email protected]98fc2b92009-05-21 14:11:5153REPOSITORY_ROOT = ""
[email protected]fb2b8eb2009-04-23 21:03:4254
55# Filename where we store repository specific information for gcl.
56CODEREVIEW_SETTINGS_FILE = "codereview.settings"
[email protected]b8260242010-08-19 17:03:1657CODEREVIEW_SETTINGS_FILE_NOT_FOUND = (
58 'No %s file found. Please add one.' % CODEREVIEW_SETTINGS_FILE)
[email protected]fb2b8eb2009-04-23 21:03:4259
60# Warning message when the change appears to be missing tests.
61MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!"
62
[email protected]98fc2b92009-05-21 14:11:5163# Global cache of files cached in GetCacheDir().
64FILES_CACHE = {}
[email protected]fb2b8eb2009-04-23 21:03:4265
[email protected]4c22d722010-05-14 19:01:2266# Valid extensions for files we want to lint.
67DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
68DEFAULT_LINT_IGNORE_REGEX = r"$^"
69
[email protected]30becc22011-03-17 01:21:2270REVIEWERS_REGEX = r'\s*R=(.+)'
[email protected]4c22d722010-05-14 19:01:2271
[email protected]e5299012010-04-07 18:02:2672def CheckHomeForFile(filename):
73 """Checks the users home dir for the existence of the given file. Returns
74 the path to the file if it's there, or None if it is not.
75 """
76 home_vars = ['HOME']
77 if sys.platform in ('cygwin', 'win32'):
78 home_vars.append('USERPROFILE')
79 for home_var in home_vars:
80 home = os.getenv(home_var)
81 if home != None:
82 full_path = os.path.join(home, filename)
83 if os.path.exists(full_path):
84 return full_path
85 return None
[email protected]fb2b8eb2009-04-23 21:03:4286
[email protected]35fe9ad2010-05-25 23:59:5487
[email protected]62fd6932010-05-27 13:13:2388def UnknownFiles():
89 """Runs svn status and returns unknown files."""
90 return [item[1] for item in SVN.CaptureStatus([]) if item[0][0] == '?']
[email protected]207fdf32009-04-28 19:57:0191
92
[email protected]fb2b8eb2009-04-23 21:03:4293def GetRepositoryRoot():
94 """Returns the top level directory of the current repository.
95
96 The directory is returned as an absolute path.
97 """
[email protected]98fc2b92009-05-21 14:11:5198 global REPOSITORY_ROOT
99 if not REPOSITORY_ROOT:
[email protected]94b1ee92009-12-19 20:27:20100 REPOSITORY_ROOT = SVN.GetCheckoutRoot(os.getcwd())
101 if not REPOSITORY_ROOT:
[email protected]5f3eee32009-09-17 00:34:30102 raise gclient_utils.Error("gcl run outside of repository")
[email protected]98fc2b92009-05-21 14:11:51103 return REPOSITORY_ROOT
[email protected]fb2b8eb2009-04-23 21:03:42104
105
106def GetInfoDir():
107 """Returns the directory where gcl info files are stored."""
[email protected]374d65e2009-05-21 14:00:52108 return os.path.join(GetRepositoryRoot(), '.svn', 'gcl_info')
109
110
111def GetChangesDir():
112 """Returns the directory where gcl change files are stored."""
113 return os.path.join(GetInfoDir(), 'changes')
[email protected]fb2b8eb2009-04-23 21:03:42114
115
[email protected]98fc2b92009-05-21 14:11:51116def GetCacheDir():
117 """Returns the directory where gcl change files are stored."""
118 return os.path.join(GetInfoDir(), 'cache')
119
120
121def GetCachedFile(filename, max_age=60*60*24*3, use_root=False):
122 """Retrieves a file from the repository and caches it in GetCacheDir() for
123 max_age seconds.
124
125 use_root: If False, look up the arborescence for the first match, otherwise go
126 directory to the root repository.
127
128 Note: The cache will be inconsistent if the same file is retrieved with both
[email protected]bb816382009-10-29 01:38:02129 use_root=True and use_root=False. Don't be stupid.
[email protected]98fc2b92009-05-21 14:11:51130 """
[email protected]98fc2b92009-05-21 14:11:51131 if filename not in FILES_CACHE:
132 # Don't try to look up twice.
133 FILES_CACHE[filename] = None
[email protected]9b613272009-04-24 01:28:28134 # First we check if we have a cached version.
[email protected]a05be0b2009-06-30 19:13:02135 try:
136 cached_file = os.path.join(GetCacheDir(), filename)
[email protected]31cb48a2011-04-04 18:01:36137 except (gclient_utils.Error, subprocess2.CalledProcessError):
[email protected]a05be0b2009-06-30 19:13:02138 return None
[email protected]98fc2b92009-05-21 14:11:51139 if (not os.path.exists(cached_file) or
[email protected]2f6a0d82010-05-12 00:03:30140 (time.time() - os.stat(cached_file).st_mtime) > max_age):
[email protected]b3b494f2010-08-31 20:40:08141 dir_info = SVN.CaptureInfo('.')
142 repo_root = dir_info['Repository Root']
[email protected]98fc2b92009-05-21 14:11:51143 if use_root:
144 url_path = repo_root
145 else:
[email protected]b3b494f2010-08-31 20:40:08146 url_path = dir_info['URL']
[email protected]9b613272009-04-24 01:28:28147 while True:
[email protected]fa44e4a2009-12-03 01:41:13148 # Look in the repository at the current level for the file.
[email protected]b6ee1672010-08-19 17:06:07149 for _ in range(5):
[email protected]b3b494f2010-08-31 20:40:08150 content = None
[email protected]b6ee1672010-08-19 17:06:07151 try:
152 # Take advantage of the fact that svn won't output to stderr in case
153 # of success but will do in case of failure so don't mind putting
154 # stderr into content_array.
155 content_array = []
[email protected]b3b494f2010-08-31 20:40:08156 svn_path = url_path + '/' + filename
[email protected]17368002010-09-01 23:36:32157 args = ['svn', 'cat', svn_path]
[email protected]02913a52010-08-25 20:50:50158 if sys.platform != 'darwin':
159 # MacOSX 10.5.2 has a bug with svn 1.4.4 that will trigger the
160 # 'Can\'t get username or password' and can be fixed easily.
161 # The fix doesn't work if the user upgraded to svn 1.6.x. Bleh.
162 # I don't have time to fix their broken stuff.
163 args.append('--non-interactive')
[email protected]17d01792010-09-01 18:07:10164 gclient_utils.CheckCallAndFilter(
165 args, cwd='.', filter_fn=content_array.append)
[email protected]b6ee1672010-08-19 17:06:07166 # Exit the loop if the file was found. Override content.
167 content = '\n'.join(content_array)
168 break
[email protected]31cb48a2011-04-04 18:01:36169 except (gclient_utils.Error, subprocess2.CalledProcessError):
[email protected]b6ee1672010-08-19 17:06:07170 if content_array[0].startswith(
171 'svn: Can\'t get username or password'):
172 ErrorExit('Your svn credentials expired. Please run svn update '
173 'to fix the cached credentials')
[email protected]3c842982010-08-20 17:26:49174 if content_array[0].startswith('svn: Can\'t get password'):
175 ErrorExit('If are using a Mac and svn --version shows 1.4.x, '
176 'please hack gcl.py to remove --non-interactive usage, it\'s'
177 'a bug on your installed copy')
[email protected]dcd15222010-12-21 22:43:19178 if (content_array[0].startswith('svn: File not found:') or
179 content_array[0].endswith('path not found')):
180 break
181 # Otherwise, fall through to trying again.
[email protected]b6ee1672010-08-19 17:06:07182 if content:
[email protected]9b613272009-04-24 01:28:28183 break
[email protected]9b613272009-04-24 01:28:28184 if url_path == repo_root:
185 # Reached the root. Abandoning search.
[email protected]98fc2b92009-05-21 14:11:51186 break
[email protected]9b613272009-04-24 01:28:28187 # Go up one level to try again.
188 url_path = os.path.dirname(url_path)
[email protected]b3b494f2010-08-31 20:40:08189 if content is not None or filename != CODEREVIEW_SETTINGS_FILE:
190 # Write a cached version even if there isn't a file, so we don't try to
191 # fetch it each time. codereview.settings must always be present so do
192 # not cache negative.
193 gclient_utils.FileWrite(cached_file, content or '')
[email protected]98fc2b92009-05-21 14:11:51194 else:
[email protected]0fca4f32009-12-18 15:14:34195 content = gclient_utils.FileRead(cached_file, 'r')
[email protected]98fc2b92009-05-21 14:11:51196 # Keep the content cached in memory.
197 FILES_CACHE[filename] = content
198 return FILES_CACHE[filename]
[email protected]9b613272009-04-24 01:28:28199
[email protected]98fc2b92009-05-21 14:11:51200
201def GetCodeReviewSetting(key):
202 """Returns a value for the given key for this repository."""
203 # Use '__just_initialized' as a flag to determine if the settings were
204 # already initialized.
[email protected]98fc2b92009-05-21 14:11:51205 if '__just_initialized' not in CODEREVIEW_SETTINGS:
[email protected]b0442182009-06-05 14:20:47206 settings_file = GetCachedFile(CODEREVIEW_SETTINGS_FILE)
207 if settings_file:
208 for line in settings_file.splitlines():
[email protected]807c4462010-07-10 00:45:28209 if not line or line.startswith('#'):
[email protected]b0442182009-06-05 14:20:47210 continue
[email protected]807c4462010-07-10 00:45:28211 if not ':' in line:
212 raise gclient_utils.Error(
213 '%s is invalid, please fix. It\'s content:\n\n%s' %
214 (CODEREVIEW_SETTINGS_FILE, settings_file))
[email protected]dd218e52010-08-23 13:02:41215 k, v = line.split(':', 1)
216 CODEREVIEW_SETTINGS[k.strip()] = v.strip()
[email protected]98fc2b92009-05-21 14:11:51217 CODEREVIEW_SETTINGS.setdefault('__just_initialized', None)
[email protected]fb2b8eb2009-04-23 21:03:42218 return CODEREVIEW_SETTINGS.get(key, "")
219
220
[email protected]fb2b8eb2009-04-23 21:03:42221def Warn(msg):
[email protected]6e29d572010-06-04 17:32:20222 print >> sys.stderr, msg
[email protected]223b7192010-06-04 18:52:58223
224
225def ErrorExit(msg):
226 print >> sys.stderr, msg
227 sys.exit(1)
[email protected]fb2b8eb2009-04-23 21:03:42228
229
[email protected]97335082011-10-20 15:00:16230def RunShellWithReturnCode(command, print_output=False):
231 """Executes a command and returns the output and the return code."""
232 p = subprocess2.Popen(
233 command, stdout=subprocess2.PIPE,
234 stderr=subprocess2.STDOUT, universal_newlines=True)
235 if print_output:
236 output_array = []
237 while True:
238 line = p.stdout.readline()
239 if not line:
240 break
241 if print_output:
242 print line.strip('\n')
243 output_array.append(line)
244 output = "".join(output_array)
245 else:
246 output = p.stdout.read()
247 p.wait()
248 p.stdout.close()
249 return output, p.returncode
250
251
252def RunShell(command, print_output=False):
253 """Executes a command and returns the output."""
254 return RunShellWithReturnCode(command, print_output)[0]
255
256
[email protected]51ee0072009-06-08 19:20:05257def FilterFlag(args, flag):
258 """Returns True if the flag is present in args list.
259
260 The flag is removed from args if present.
261 """
262 if flag in args:
263 args.remove(flag)
264 return True
265 return False
266
267
[email protected]be0d1ca2009-05-12 19:23:02268class ChangeInfo(object):
[email protected]fb2b8eb2009-04-23 21:03:42269 """Holds information about a changelist.
270
[email protected]32ba2602009-06-06 18:44:48271 name: change name.
272 issue: the Rietveld issue number or 0 if it hasn't been uploaded yet.
273 patchset: the Rietveld latest patchset number or 0.
[email protected]fb2b8eb2009-04-23 21:03:42274 description: the description.
275 files: a list of 2 tuple containing (status, filename) of changed files,
276 with paths being relative to the top repository directory.
[email protected]8d5c9a52009-06-12 15:59:08277 local_root: Local root directory
[email protected]bf1fdca2010-11-01 18:05:36278 rietveld: rietveld server for this change
[email protected]fb2b8eb2009-04-23 21:03:42279 """
[email protected]bf1fdca2010-11-01 18:05:36280 # Kept for unit test support. This is for the old format, it's deprecated.
[email protected]428342a2011-11-10 15:46:33281 SEPARATOR = "\n-----\n"
[email protected]32ba2602009-06-06 18:44:48282
[email protected]ea452b32009-11-22 20:04:31283 def __init__(self, name, issue, patchset, description, files, local_root,
[email protected]6493ed12011-04-14 13:51:00284 rietveld_url, needs_upload):
[email protected]fb2b8eb2009-04-23 21:03:42285 self.name = name
[email protected]32ba2602009-06-06 18:44:48286 self.issue = int(issue)
287 self.patchset = int(patchset)
[email protected]20254fc2011-03-22 18:28:59288 self._description = None
289 self._subject = None
290 self._reviewers = None
[email protected]30becc22011-03-17 01:21:22291 self._set_description(description)
[email protected]be0d1ca2009-05-12 19:23:02292 if files is None:
293 files = []
[email protected]17f59f22009-06-12 13:27:24294 self._files = files
[email protected]fb2b8eb2009-04-23 21:03:42295 self.patch = None
[email protected]8d5c9a52009-06-12 15:59:08296 self._local_root = local_root
[email protected]ea452b32009-11-22 20:04:31297 self.needs_upload = needs_upload
[email protected]cab38e92011-04-09 00:30:51298 self.rietveld = rietveld_url
[email protected]bf1fdca2010-11-01 18:05:36299 if not self.rietveld:
300 # Set the default value.
301 self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER')
[email protected]cab38e92011-04-09 00:30:51302 self._rpc_server = None
[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
[email protected]cab38e92011-04-09 00:30:51386 def RpcServer(self):
387 if not self._rpc_server:
388 if not self.rietveld:
389 ErrorExit(CODEREVIEW_SETTINGS_FILE_NOT_FOUND)
390 self._rpc_server = rietveld.Rietveld(self.rietveld, None, None)
391 return self._rpc_server
392
[email protected]fb2b8eb2009-04-23 21:03:42393 def CloseIssue(self):
394 """Closes the Rietveld issue for this changelist."""
[email protected]41db6ef2010-11-17 19:42:34395 # Newer versions of Rietveld require us to pass an XSRF token to POST, so
396 # we fetch it from the server.
397 xsrf_token = self.SendToRietveld(
398 '/xsrf_token',
399 extra_headers={'X-Requesting-XSRF-Token': '1'})
400
401 # You cannot close an issue with a GET.
402 # We pass an empty string for the data so it is a POST rather than a GET.
403 data = [("description", self.description),
404 ("xsrf_token", xsrf_token)]
[email protected]fb2b8eb2009-04-23 21:03:42405 ctype, body = upload.EncodeMultipartFormData(data, [])
[email protected]41db6ef2010-11-17 19:42:34406 self.SendToRietveld('/%d/close' % self.issue, payload=body,
407 content_type=ctype)
[email protected]fb2b8eb2009-04-23 21:03:42408
409 def UpdateRietveldDescription(self):
410 """Sets the description for an issue on Rietveld."""
411 data = [("description", self.description),]
412 ctype, body = upload.EncodeMultipartFormData(data, [])
[email protected]41db6ef2010-11-17 19:42:34413 self.SendToRietveld('/%d/description' % self.issue, payload=body,
414 content_type=ctype)
[email protected]bf1fdca2010-11-01 18:05:36415
416 def GetIssueDescription(self):
417 """Returns the issue description from Rietveld."""
418 return self.SendToRietveld('/%d/description' % self.issue)
419
420 def PrimeLint(self):
421 """Do background work on Rietveld to lint the file so that the results are
422 ready when the issue is viewed."""
423 if self.issue and self.patchset:
424 self.SendToRietveld('/lint/issue%s_%s' % (self.issue, self.patchset),
425 timeout=1)
426
[email protected]41db6ef2010-11-17 19:42:34427 def SendToRietveld(self, request_path, timeout=None, **kwargs):
[email protected]bf1fdca2010-11-01 18:05:36428 """Send a POST/GET to Rietveld. Returns the response body."""
[email protected]bf1fdca2010-11-01 18:05:36429 try:
[email protected]cab38e92011-04-09 00:30:51430 return self.RpcServer().Send(request_path, timeout=timeout, **kwargs)
[email protected]bf1fdca2010-11-01 18:05:36431 except urllib2.URLError:
432 if timeout is None:
433 ErrorExit('Error accessing url %s' % request_path)
434 else:
435 return None
[email protected]fb2b8eb2009-04-23 21:03:42436
437 def MissingTests(self):
438 """Returns True if the change looks like it needs unit tests but has none.
439
440 A change needs unit tests if it contains any new source files or methods.
441 """
442 SOURCE_SUFFIXES = [".cc", ".cpp", ".c", ".m", ".mm"]
443 # Ignore third_party entirely.
[email protected]e3608df2009-11-10 20:22:57444 files = [f for f in self._NonDeletedFileList()
445 if f.find("third_party") == -1]
446 added_files = [f for f in self._AddedFileList()
447 if f.find("third_party") == -1]
[email protected]fb2b8eb2009-04-23 21:03:42448
449 # If the change is entirely in third_party, we're done.
450 if len(files) == 0:
451 return False
452
453 # Any new or modified test files?
454 # A test file's name ends with "test.*" or "tests.*".
455 test_files = [test for test in files
456 if os.path.splitext(test)[0].rstrip("s").endswith("test")]
457 if len(test_files) > 0:
458 return False
459
460 # Any new source files?
[email protected]e3608df2009-11-10 20:22:57461 source_files = [item for item in added_files
462 if os.path.splitext(item)[1] in SOURCE_SUFFIXES]
[email protected]fb2b8eb2009-04-23 21:03:42463 if len(source_files) > 0:
464 return True
465
466 # Do the long test, checking the files for new methods.
467 return self._HasNewMethod()
468
469 def _HasNewMethod(self):
470 """Returns True if the changeset contains any new functions, or if a
471 function signature has been changed.
472
473 A function is identified by starting flush left, containing a "(" before
474 the next flush-left line, and either ending with "{" before the next
475 flush-left line or being followed by an unindented "{".
476
477 Currently this returns True for new methods, new static functions, and
478 methods or functions whose signatures have been changed.
479
480 Inline methods added to header files won't be detected by this. That's
481 acceptable for purposes of determining if a unit test is needed, since
482 inline methods should be trivial.
483 """
484 # To check for methods added to source or header files, we need the diffs.
485 # We'll generate them all, since there aren't likely to be many files
486 # apart from source and headers; besides, we'll want them all if we're
487 # uploading anyway.
488 if self.patch is None:
[email protected]17f59f22009-06-12 13:27:24489 self.patch = GenerateDiff(self.GetFileNames())
[email protected]fb2b8eb2009-04-23 21:03:42490
491 definition = ""
492 for line in self.patch.splitlines():
493 if not line.startswith("+"):
494 continue
495 line = line.strip("+").rstrip(" \t")
496 # Skip empty lines, comments, and preprocessor directives.
497 # TODO(pamg): Handle multiline comments if it turns out to be a problem.
498 if line == "" or line.startswith("/") or line.startswith("#"):
499 continue
500
501 # A possible definition ending with "{" is complete, so check it.
502 if definition.endswith("{"):
503 if definition.find("(") != -1:
504 return True
505 definition = ""
506
507 # A { or an indented line, when we're in a definition, continues it.
508 if (definition != "" and
509 (line == "{" or line.startswith(" ") or line.startswith("\t"))):
510 definition += line
511
512 # A flush-left line starts a new possible function definition.
513 elif not line.startswith(" ") and not line.startswith("\t"):
514 definition = line
515
516 return False
517
[email protected]32ba2602009-06-06 18:44:48518 @staticmethod
[email protected]8d5c9a52009-06-12 15:59:08519 def Load(changename, local_root, fail_on_not_found, update_status):
[email protected]32ba2602009-06-06 18:44:48520 """Gets information about a changelist.
[email protected]fb2b8eb2009-04-23 21:03:42521
[email protected]32ba2602009-06-06 18:44:48522 Args:
523 fail_on_not_found: if True, this function will quit the program if the
524 changelist doesn't exist.
525 update_status: if True, the svn status will be updated for all the files
526 and unchanged files will be removed.
527
528 Returns: a ChangeInfo object.
529 """
530 info_file = GetChangelistInfoFile(changename)
531 if not os.path.exists(info_file):
532 if fail_on_not_found:
533 ErrorExit("Changelist " + changename + " not found.")
[email protected]6493ed12011-04-14 13:51:00534 return ChangeInfo(changename, 0, 0, '', None, local_root, None, False)
[email protected]7af6c4d2011-09-26 21:04:18535 content = gclient_utils.FileRead(info_file)
[email protected]8a8ea022010-11-01 13:27:32536 save = False
537 try:
[email protected]fa3843e2010-11-01 13:34:37538 values = ChangeInfo._LoadNewFormat(content)
[email protected]8a8ea022010-11-01 13:27:32539 except ValueError:
[email protected]fa3843e2010-11-01 13:34:37540 try:
541 values = ChangeInfo._LoadOldFormat(content)
542 save = True
543 except ValueError:
544 ErrorExit(
545 ('Changelist file %s is corrupt.\n'
546 'Either run "gcl delete %s" or manually edit the file') % (
547 info_file, changename))
[email protected]8a8ea022010-11-01 13:27:32548 files = values['files']
[email protected]32ba2602009-06-06 18:44:48549 if update_status:
[email protected]79b7ef02010-11-01 13:25:13550 for item in files[:]:
[email protected]e3608df2009-11-10 20:22:57551 filename = os.path.join(local_root, item[1])
[email protected]5aeb7dd2009-11-17 18:09:01552 status_result = SVN.CaptureStatus(filename)
[email protected]32ba2602009-06-06 18:44:48553 if not status_result or not status_result[0][0]:
554 # File has been reverted.
555 save = True
[email protected]e3608df2009-11-10 20:22:57556 files.remove(item)
[email protected]32ba2602009-06-06 18:44:48557 continue
558 status = status_result[0][0]
[email protected]e3608df2009-11-10 20:22:57559 if status != item[0]:
[email protected]32ba2602009-06-06 18:44:48560 save = True
[email protected]e3608df2009-11-10 20:22:57561 files[files.index(item)] = (status, item[1])
[email protected]6493ed12011-04-14 13:51:00562 change_info = ChangeInfo(
563 changename,
564 values['issue'],
565 values['patchset'],
566 values['description'],
567 files,
568 local_root,
569 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)
[email protected]428342a2011-11-10 15:46:33579 # SEPARATOR\n
[email protected]bf1fdca2010-11-01 18:05:36580 # filepath1\n
581 # filepath2\n
582 # .
583 # .
584 # filepathn\n
[email protected]428342a2011-11-10 15:46:33585 # SEPARATOR\n
[email protected]bf1fdca2010-11-01 18:05:36586 # description
[email protected]428342a2011-11-10 15:46:33587 split_data = content.split(ChangeInfo.SEPARATOR, 2)
[email protected]8a8ea022010-11-01 13:27:32588 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]6493ed12011-04-14 13:51:00629 aggregate_change_info = ChangeInfo(
630 changenames, 0, 0, '', None, local_root, None, False)
[email protected]fb2b8eb2009-04-23 21:03:42631 for change in changes:
[email protected]6493ed12011-04-14 13:51:00632 aggregate_change_info._files += ChangeInfo.Load(
633 change, local_root, fail_on_not_found, update_status).GetFiles()
[email protected]fb2b8eb2009-04-23 21:03:42634 return aggregate_change_info
635
636
[email protected]fb2b8eb2009-04-23 21:03:42637def GetCLs():
638 """Returns a list of all the changelists in this repository."""
[email protected]374d65e2009-05-21 14:00:52639 cls = os.listdir(GetChangesDir())
[email protected]fb2b8eb2009-04-23 21:03:42640 if CODEREVIEW_SETTINGS_FILE in cls:
641 cls.remove(CODEREVIEW_SETTINGS_FILE)
642 return cls
643
644
645def GenerateChangeName():
646 """Generate a random changelist name."""
647 random.seed()
648 current_cl_names = GetCLs()
649 while True:
650 cl_name = (random.choice(string.ascii_lowercase) +
651 random.choice(string.digits) +
652 random.choice(string.ascii_lowercase) +
653 random.choice(string.digits))
654 if cl_name not in current_cl_names:
655 return cl_name
656
657
658def GetModifiedFiles():
659 """Returns a set that maps from changelist name to (status,filename) tuples.
660
661 Files not in a changelist have an empty changelist name. Filenames are in
662 relation to the top level directory of the current repository. Note that
663 only the current directory and subdirectories are scanned, in order to
664 improve performance while still being flexible.
665 """
666 files = {}
667
668 # Since the files are normalized to the root folder of the repositary, figure
669 # out what we need to add to the paths.
670 dir_prefix = os.getcwd()[len(GetRepositoryRoot()):].strip(os.sep)
671
672 # Get a list of all files in changelists.
673 files_in_cl = {}
674 for cl in GetCLs():
[email protected]8d5c9a52009-06-12 15:59:08675 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(),
676 fail_on_not_found=True, update_status=False)
[email protected]17f59f22009-06-12 13:27:24677 for status, filename in change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42678 files_in_cl[filename] = change_info.name
679
680 # Get all the modified files.
[email protected]5aeb7dd2009-11-17 18:09:01681 status_result = SVN.CaptureStatus(None)
[email protected]207fdf32009-04-28 19:57:01682 for line in status_result:
683 status = line[0]
684 filename = line[1]
685 if status[0] == "?":
[email protected]fb2b8eb2009-04-23 21:03:42686 continue
[email protected]fb2b8eb2009-04-23 21:03:42687 if dir_prefix:
688 filename = os.path.join(dir_prefix, filename)
689 change_list_name = ""
690 if filename in files_in_cl:
691 change_list_name = files_in_cl[filename]
692 files.setdefault(change_list_name, []).append((status, filename))
693
694 return files
695
696
697def GetFilesNotInCL():
698 """Returns a list of tuples (status,filename) that aren't in any changelists.
699
700 See docstring of GetModifiedFiles for information about path of files and
701 which directories are scanned.
702 """
703 modified_files = GetModifiedFiles()
704 if "" not in modified_files:
705 return []
706 return modified_files[""]
707
708
[email protected]4c22d722010-05-14 19:01:22709def ListFiles(show_unknown_files):
[email protected]fb2b8eb2009-04-23 21:03:42710 files = GetModifiedFiles()
711 cl_keys = files.keys()
712 cl_keys.sort()
713 for cl_name in cl_keys:
[email protected]88c32d82009-10-12 18:24:05714 if not cl_name:
715 continue
716 note = ""
717 change_info = ChangeInfo.Load(cl_name, GetRepositoryRoot(),
718 fail_on_not_found=True, update_status=False)
719 if len(change_info.GetFiles()) != len(files[cl_name]):
720 note = " (Note: this changelist contains files outside this directory)"
721 print "\n--- Changelist " + cl_name + note + ":"
[email protected]e3608df2009-11-10 20:22:57722 for filename in files[cl_name]:
723 print "".join(filename)
[email protected]88c32d82009-10-12 18:24:05724 if show_unknown_files:
[email protected]62fd6932010-05-27 13:13:23725 unknown_files = UnknownFiles()
[email protected]88c32d82009-10-12 18:24:05726 if (files.get('') or (show_unknown_files and len(unknown_files))):
727 print "\n--- Not in any changelist:"
[email protected]e3608df2009-11-10 20:22:57728 for item in files.get('', []):
729 print "".join(item)
[email protected]88c32d82009-10-12 18:24:05730 if show_unknown_files:
[email protected]e3608df2009-11-10 20:22:57731 for filename in unknown_files:
732 print "? %s" % filename
[email protected]4c22d722010-05-14 19:01:22733 return 0
[email protected]fb2b8eb2009-04-23 21:03:42734
735
[email protected]fb2b8eb2009-04-23 21:03:42736def GenerateDiff(files, root=None):
[email protected]f2f9d552009-12-22 00:12:57737 return SVN.GenerateDiff(files, root=root)
[email protected]fb2b8eb2009-04-23 21:03:42738
[email protected]51ee0072009-06-08 19:20:05739
740def OptionallyDoPresubmitChecks(change_info, committing, args):
741 if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"):
[email protected]30becc22011-03-17 01:21:22742 return presubmit_support.PresubmitOutput()
[email protected]b0dfd352009-06-10 14:12:54743 return DoPresubmitChecks(change_info, committing, True)
[email protected]51ee0072009-06-08 19:20:05744
745
[email protected]62fd6932010-05-27 13:13:23746def defer_attributes(a, b):
747 """Copy attributes from an object (like a function) to another."""
748 for x in dir(a):
749 if not getattr(b, x, None):
750 setattr(b, x, getattr(a, x))
751
752
[email protected]35fe9ad2010-05-25 23:59:54753def need_change(function):
754 """Converts args -> change_info."""
[email protected]3a174252010-10-29 15:54:51755 # pylint: disable=W0612,W0621
[email protected]35fe9ad2010-05-25 23:59:54756 def hook(args):
757 if not len(args) == 1:
758 ErrorExit("You need to pass a change list name")
759 change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(), True, True)
760 return function(change_info)
[email protected]62fd6932010-05-27 13:13:23761 defer_attributes(function, hook)
762 hook.need_change = True
763 hook.no_args = True
[email protected]35fe9ad2010-05-25 23:59:54764 return hook
765
766
[email protected]62fd6932010-05-27 13:13:23767def need_change_and_args(function):
768 """Converts args -> change_info."""
[email protected]3a174252010-10-29 15:54:51769 # pylint: disable=W0612,W0621
[email protected]62fd6932010-05-27 13:13:23770 def hook(args):
[email protected]e56fe822010-05-28 20:36:57771 if not args:
772 ErrorExit("You need to pass a change list name")
[email protected]62fd6932010-05-27 13:13:23773 change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
774 return function(change_info, args)
775 defer_attributes(function, hook)
776 hook.need_change = True
777 return hook
778
779
780def no_args(function):
781 """Make sure no args are passed."""
[email protected]3a174252010-10-29 15:54:51782 # pylint: disable=W0612,W0621
[email protected]62fd6932010-05-27 13:13:23783 def hook(args):
784 if args:
785 ErrorExit("Doesn't support arguments")
786 return function()
787 defer_attributes(function, hook)
788 hook.no_args = True
789 return hook
790
791
792def attrs(**kwargs):
793 """Decorate a function with new attributes."""
794 def decorate(function):
795 for k in kwargs:
796 setattr(function, k, kwargs[k])
797 return function
798 return decorate
799
800
801@no_args
802def CMDopened():
803 """Lists modified files in the current directory down."""
804 return ListFiles(False)
805
806
807@no_args
808def CMDstatus():
809 """Lists modified and unknown files in the current directory down."""
810 return ListFiles(True)
811
812
813@need_change_and_args
[email protected]2b9aa8e2010-08-25 20:01:42814@attrs(usage='[--no_presubmit] [--no_watchlists]')
[email protected]62fd6932010-05-27 13:13:23815def CMDupload(change_info, args):
816 """Uploads the changelist to the server for review.
817
[email protected]5db5ba52010-07-20 15:50:47818 This does not submit a try job; use gcl try to submit a try job.
[email protected]62fd6932010-05-27 13:13:23819 """
[email protected]10ccd112010-08-24 16:48:42820 if '-s' in args or '--server' in args:
821 ErrorExit('Don\'t use the -s flag, fix codereview.settings instead')
[email protected]17f59f22009-06-12 13:27:24822 if not change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42823 print "Nothing to upload, changelist is empty."
[email protected]35fe9ad2010-05-25 23:59:54824 return 0
[email protected]30becc22011-03-17 01:21:22825
826 output = OptionallyDoPresubmitChecks(change_info, False, args)
827 if not output.should_continue():
[email protected]35fe9ad2010-05-25 23:59:54828 return 1
[email protected]62fd6932010-05-27 13:13:23829 no_watchlists = (FilterFlag(args, "--no_watchlists") or
830 FilterFlag(args, "--no-watchlists"))
[email protected]fb2b8eb2009-04-23 21:03:42831
832 # Map --send-mail to --send_mail
[email protected]51ee0072009-06-08 19:20:05833 if FilterFlag(args, "--send-mail"):
[email protected]fb2b8eb2009-04-23 21:03:42834 args.append("--send_mail")
835
[email protected]fb2b8eb2009-04-23 21:03:42836 upload_arg = ["upload.py", "-y"]
[email protected]bf1fdca2010-11-01 18:05:36837 upload_arg.append("--server=%s" % change_info.rietveld)
[email protected]30becc22011-03-17 01:21:22838
839 reviewers = change_info.reviewers or output.reviewers
840 if (reviewers and
841 not any(arg.startswith('-r') or arg.startswith('--reviewer') for
842 arg in args)):
843 upload_arg.append('--reviewers=%s' % ','.join(reviewers))
844
[email protected]fb2b8eb2009-04-23 21:03:42845 upload_arg.extend(args)
846
847 desc_file = ""
848 if change_info.issue: # Uploading a new patchset.
849 found_message = False
850 for arg in args:
851 if arg.startswith("--message") or arg.startswith("-m"):
852 found_message = True
853 break
854
855 if not found_message:
856 upload_arg.append("--message=''")
857
[email protected]32ba2602009-06-06 18:44:48858 upload_arg.append("--issue=%d" % change_info.issue)
[email protected]fb2b8eb2009-04-23 21:03:42859 else: # First time we upload.
860 handle, desc_file = tempfile.mkstemp(text=True)
861 os.write(handle, change_info.description)
862 os.close(handle)
863
[email protected]b2ab4942009-06-11 21:39:19864 # Watchlist processing -- CC people interested in this changeset
865 # http://dev.chromium.org/developers/contributing-code/watchlists
866 if not no_watchlists:
867 import watchlists
[email protected]17f59f22009-06-12 13:27:24868 watchlist = watchlists.Watchlists(change_info.GetLocalRoot())
[email protected]07f01862009-06-12 16:51:08869 watchers = watchlist.GetWatchersForPaths(change_info.GetFileNames())
[email protected]b2ab4942009-06-11 21:39:19870
[email protected]fb2b8eb2009-04-23 21:03:42871 cc_list = GetCodeReviewSetting("CC_LIST")
[email protected]b2ab4942009-06-11 21:39:19872 if not no_watchlists and watchers:
[email protected]6e29d572010-06-04 17:32:20873 # Filter out all empty elements and join by ','
874 cc_list = ','.join(filter(None, [cc_list] + watchers))
[email protected]fb2b8eb2009-04-23 21:03:42875 if cc_list:
876 upload_arg.append("--cc=" + cc_list)
877 upload_arg.append("--description_file=" + desc_file + "")
[email protected]30becc22011-03-17 01:21:22878 if change_info.subject:
[email protected]b68786e2011-03-17 01:39:09879 upload_arg.append("--message=" + change_info.subject)
[email protected]fb2b8eb2009-04-23 21:03:42880
[email protected]83b6e4b2010-03-09 03:16:14881 if GetCodeReviewSetting("PRIVATE") == "True":
882 upload_arg.append("--private")
883
[email protected]fb2b8eb2009-04-23 21:03:42884 # Change the current working directory before calling upload.py so that it
885 # shows the correct base.
886 previous_cwd = os.getcwd()
[email protected]17f59f22009-06-12 13:27:24887 os.chdir(change_info.GetLocalRoot())
[email protected]fb2b8eb2009-04-23 21:03:42888 # If we have a lot of files with long paths, then we won't be able to fit
889 # the command to "svn diff". Instead, we generate the diff manually for
890 # each file and concatenate them before passing it to upload.py.
891 if change_info.patch is None:
[email protected]17f59f22009-06-12 13:27:24892 change_info.patch = GenerateDiff(change_info.GetFileNames())
[email protected]9ce0dff2011-04-04 17:56:50893 try:
894 issue, patchset = upload.RealMain(upload_arg, change_info.patch)
895 except KeyboardInterrupt:
896 sys.exit(1)
[email protected]32ba2602009-06-06 18:44:48897 if issue and patchset:
898 change_info.issue = int(issue)
899 change_info.patchset = int(patchset)
[email protected]fb2b8eb2009-04-23 21:03:42900 change_info.Save()
901
902 if desc_file:
903 os.remove(desc_file)
[email protected]bf1fdca2010-11-01 18:05:36904 change_info.PrimeLint()
[email protected]57e78552009-09-11 23:04:30905 os.chdir(previous_cwd)
[email protected]02287952010-07-23 22:36:58906 print "*** Upload does not submit a try; use gcl try to submit a try. ***"
[email protected]35fe9ad2010-05-25 23:59:54907 return 0
[email protected]fb2b8eb2009-04-23 21:03:42908
[email protected]fb2b8eb2009-04-23 21:03:42909
[email protected]8e13a092010-11-02 19:06:06910@need_change_and_args
911@attrs(usage='[--upload]')
912def CMDpresubmit(change_info, args):
[email protected]62fd6932010-05-27 13:13:23913 """Runs presubmit checks on the change.
914
915 The actual presubmit code is implemented in presubmit_support.py and looks
916 for PRESUBMIT.py files."""
[email protected]17f59f22009-06-12 13:27:24917 if not change_info.GetFiles():
[email protected]8e13a092010-11-02 19:06:06918 print('Nothing to presubmit check, changelist is empty.')
[email protected]4c22d722010-05-14 19:01:22919 return 0
[email protected]8e13a092010-11-02 19:06:06920 parser = optparse.OptionParser()
921 parser.add_option('--upload', action='store_true')
922 options, args = parser.parse_args(args)
923 if args:
924 parser.error('Unrecognized args: %s' % args)
925 if options.upload:
926 print('*** Presubmit checks for UPLOAD would report: ***')
927 return not DoPresubmitChecks(change_info, False, False)
928 else:
929 print('*** Presubmit checks for COMMIT would report: ***')
930 return not DoPresubmitChecks(change_info, True, False)
[email protected]fb2b8eb2009-04-23 21:03:42931
932
933def TryChange(change_info, args, swallow_exception):
934 """Create a diff file of change_info and send it to the try server."""
935 try:
936 import trychange
937 except ImportError:
938 if swallow_exception:
[email protected]35fe9ad2010-05-25 23:59:54939 return 1
[email protected]fb2b8eb2009-04-23 21:03:42940 ErrorExit("You need to install trychange.py to use the try server.")
941
[email protected]18111352009-12-20 17:21:28942 trychange_args = []
[email protected]fb2b8eb2009-04-23 21:03:42943 if change_info:
[email protected]18111352009-12-20 17:21:28944 trychange_args.extend(['--name', change_info.name])
[email protected]32ba2602009-06-06 18:44:48945 if change_info.issue:
946 trychange_args.extend(["--issue", str(change_info.issue)])
947 if change_info.patchset:
948 trychange_args.extend(["--patchset", str(change_info.patchset)])
[email protected]15169952011-09-27 14:30:53949 change = presubmit_support.SvnChange(change_info.name,
950 change_info.description,
951 change_info.GetLocalRoot(),
952 change_info.GetFiles(),
953 change_info.issue,
954 change_info.patchset,
955 None)
[email protected]fb2b8eb2009-04-23 21:03:42956 else:
[email protected]15169952011-09-27 14:30:53957 change = None
958
[email protected]d3e57542011-03-15 09:43:47959 trychange_args.extend(args)
[email protected]d0891922010-05-31 18:33:16960 return trychange.TryChange(
961 trychange_args,
[email protected]15169952011-09-27 14:30:53962 change=change,
[email protected]d0891922010-05-31 18:33:16963 swallow_exception=swallow_exception,
964 prog='gcl try',
965 extra_epilog='\n'
966 'When called from gcl, use the format gcl try <change_name>.\n')
[email protected]fb2b8eb2009-04-23 21:03:42967
968
[email protected]62fd6932010-05-27 13:13:23969@need_change_and_args
970@attrs(usage='[--no_presubmit]')
[email protected]4357af22010-05-27 15:42:34971def CMDcommit(change_info, args):
[email protected]62fd6932010-05-27 13:13:23972 """Commits the changelist to the repository."""
[email protected]17f59f22009-06-12 13:27:24973 if not change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42974 print "Nothing to commit, changelist is empty."
[email protected]4c22d722010-05-14 19:01:22975 return 1
[email protected]73ac0f12011-03-17 23:34:22976
977 output = OptionallyDoPresubmitChecks(change_info, True, args)
978 if not output.should_continue():
[email protected]4c22d722010-05-14 19:01:22979 return 1
[email protected]fb2b8eb2009-04-23 21:03:42980
[email protected]1bb04aa2009-06-01 17:52:11981 # We face a problem with svn here: Let's say change 'bleh' modifies
982 # svn:ignore on dir1\. but another unrelated change 'pouet' modifies
983 # dir1\foo.cc. When the user `gcl commit bleh`, foo.cc is *also committed*.
984 # The only fix is to use --non-recursive but that has its issues too:
985 # Let's say if dir1 is deleted, --non-recursive must *not* be used otherwise
986 # you'll get "svn: Cannot non-recursively commit a directory deletion of a
987 # directory with child nodes". Yay...
988 commit_cmd = ["svn", "commit"]
[email protected]fb2b8eb2009-04-23 21:03:42989 if change_info.issue:
990 # Get the latest description from Rietveld.
[email protected]bf1fdca2010-11-01 18:05:36991 change_info.description = change_info.GetIssueDescription()
[email protected]fb2b8eb2009-04-23 21:03:42992
993 commit_message = change_info.description.replace('\r\n', '\n')
994 if change_info.issue:
[email protected]bf1fdca2010-11-01 18:05:36995 server = change_info.rietveld
[email protected]fcff9272010-04-29 23:56:19996 if not server.startswith("http://") and not server.startswith("https://"):
997 server = "http://" + server
998 commit_message += ('\nReview URL: %s/%d' % (server, change_info.issue))
[email protected]fb2b8eb2009-04-23 21:03:42999
1000 handle, commit_filename = tempfile.mkstemp(text=True)
1001 os.write(handle, commit_message)
1002 os.close(handle)
[email protected]5d0fc9a2011-12-01 00:42:561003 try:
1004 handle, targets_filename = tempfile.mkstemp(text=True)
1005 os.write(handle, "\n".join(change_info.GetFileNames()))
1006 os.close(handle)
1007 try:
1008 commit_cmd += ['--file=' + commit_filename]
1009 commit_cmd += ['--targets=' + targets_filename]
1010 # Change the current working directory before calling commit.
1011 previous_cwd = os.getcwd()
1012 os.chdir(change_info.GetLocalRoot())
1013 output = ''
1014 try:
1015 output = RunShell(commit_cmd, True)
1016 except subprocess2.CalledProcessError, e:
1017 ErrorExit('Commit failed.\n%s' % e)
1018 finally:
1019 os.remove(commit_filename)
1020 finally:
1021 os.remove(targets_filename)
[email protected]fb2b8eb2009-04-23 21:03:421022 if output.find("Committed revision") != -1:
1023 change_info.Delete()
1024
1025 if change_info.issue:
1026 revision = re.compile(".*?\nCommitted revision (\d+)",
1027 re.DOTALL).match(output).group(1)
1028 viewvc_url = GetCodeReviewSetting("VIEW_VC")
[email protected]30becc22011-03-17 01:21:221029 change_info.description += '\n'
[email protected]fb2b8eb2009-04-23 21:03:421030 if viewvc_url:
1031 change_info.description += "\nCommitted: " + viewvc_url + revision
1032 change_info.CloseIssue()
1033 os.chdir(previous_cwd)
[email protected]4c22d722010-05-14 19:01:221034 return 0
[email protected]fb2b8eb2009-04-23 21:03:421035
[email protected]2c8d4b22009-06-06 21:03:101036
[email protected]35fe9ad2010-05-25 23:59:541037def CMDchange(args):
[email protected]62fd6932010-05-27 13:13:231038 """Creates or edits a changelist.
1039
1040 Only scans the current directory and subdirectories."""
[email protected]35fe9ad2010-05-25 23:59:541041 if len(args) == 0:
1042 # Generate a random changelist name.
1043 changename = GenerateChangeName()
1044 elif args[0] == '--force':
1045 changename = GenerateChangeName()
1046 else:
1047 changename = args[0]
1048 change_info = ChangeInfo.Load(changename, GetRepositoryRoot(), False, True)
[email protected]d36b3ed2009-11-09 18:51:421049
1050 # Verify the user is running the change command from a read-write checkout.
[email protected]5aeb7dd2009-11-17 18:09:011051 svn_info = SVN.CaptureInfo('.')
[email protected]d36b3ed2009-11-09 18:51:421052 if not svn_info:
1053 ErrorExit("Current checkout is unversioned. Please retry with a versioned "
1054 "directory.")
[email protected]d36b3ed2009-11-09 18:51:421055
[email protected]35fe9ad2010-05-25 23:59:541056 if len(args) == 2:
[email protected]c8f3cf82010-09-09 20:00:121057 if not os.path.isfile(args[1]):
1058 ErrorExit('The change "%s" doesn\'t exist.' % args[1])
[email protected]35fe9ad2010-05-25 23:59:541059 f = open(args[1], 'rU')
[email protected]9ce98222009-10-19 20:24:171060 override_description = f.read()
1061 f.close()
1062 else:
1063 override_description = None
[email protected]5aeb7dd2009-11-17 18:09:011064
[email protected]ea452b32009-11-22 20:04:311065 if change_info.issue and not change_info.NeedsUpload():
[email protected]fb2b8eb2009-04-23 21:03:421066 try:
[email protected]bf1fdca2010-11-01 18:05:361067 description = change_info.GetIssueDescription()
[email protected]fb2b8eb2009-04-23 21:03:421068 except urllib2.HTTPError, err:
1069 if err.code == 404:
1070 # The user deleted the issue in Rietveld, so forget the old issue id.
1071 description = change_info.description
[email protected]2c8d4b22009-06-06 21:03:101072 change_info.issue = 0
[email protected]fb2b8eb2009-04-23 21:03:421073 change_info.Save()
1074 else:
1075 ErrorExit("Error getting the description from Rietveld: " + err)
1076 else:
[email protected]85532fc2009-06-04 22:36:531077 if override_description:
1078 description = override_description
1079 else:
1080 description = change_info.description
[email protected]fb2b8eb2009-04-23 21:03:421081
1082 other_files = GetFilesNotInCL()
[email protected]bfd09ce2009-08-05 21:17:231083
[email protected]f0dfba32009-08-07 22:03:371084 # Edited files (as opposed to files with only changed properties) will have
1085 # a letter for the first character in the status string.
[email protected]85532fc2009-06-04 22:36:531086 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE)
[email protected]f0dfba32009-08-07 22:03:371087 affected_files = [x for x in other_files if file_re.match(x[0])]
1088 unaffected_files = [x for x in other_files if not file_re.match(x[0])]
[email protected]fb2b8eb2009-04-23 21:03:421089
[email protected]20254fc2011-03-22 18:28:591090 description = description.rstrip() + '\n'
[email protected]30becc22011-03-17 01:21:221091
[email protected]fb2b8eb2009-04-23 21:03:421092 separator1 = ("\n---All lines above this line become the description.\n"
[email protected]17f59f22009-06-12 13:27:241093 "---Repository Root: " + change_info.GetLocalRoot() + "\n"
[email protected]fb2b8eb2009-04-23 21:03:421094 "---Paths in this changelist (" + change_info.name + "):\n")
1095 separator2 = "\n\n---Paths modified but not in any changelist:\n\n"
[email protected]f1c2f432011-09-19 23:29:081096
[email protected]0e0436a2011-10-25 13:32:411097 text = (description + separator1 + '\n' +
[email protected]20254fc2011-03-22 18:28:591098 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()]))
[email protected]f0dfba32009-08-07 22:03:371099
1100 if change_info.Exists():
[email protected]20254fc2011-03-22 18:28:591101 text += (separator2 +
1102 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n')
[email protected]f0dfba32009-08-07 22:03:371103 else:
[email protected]20254fc2011-03-22 18:28:591104 text += ('\n'.join([f[0] + f[1] for f in affected_files]) + '\n' +
1105 separator2)
1106 text += '\n'.join([f[0] + f[1] for f in unaffected_files]) + '\n'
[email protected]fb2b8eb2009-04-23 21:03:421107
[email protected]0e0436a2011-10-25 13:32:411108 result = gclient_utils.RunEditor(text, False)
[email protected]fb2b8eb2009-04-23 21:03:421109 if not result:
[email protected]0e0436a2011-10-25 13:32:411110 ErrorExit('Running editor failed')
[email protected]fb2b8eb2009-04-23 21:03:421111
1112 split_result = result.split(separator1, 1)
1113 if len(split_result) != 2:
[email protected]0e0436a2011-10-25 13:32:411114 ErrorExit("Don't modify the text starting with ---!\n\n%r" % result)
[email protected]fb2b8eb2009-04-23 21:03:421115
[email protected]ea452b32009-11-22 20:04:311116 # Update the CL description if it has changed.
[email protected]fb2b8eb2009-04-23 21:03:421117 new_description = split_result[0]
1118 cl_files_text = split_result[1]
[email protected]20254fc2011-03-22 18:28:591119 if new_description != description or override_description:
1120 change_info.description = new_description
[email protected]ea452b32009-11-22 20:04:311121 change_info.needs_upload = True
[email protected]fb2b8eb2009-04-23 21:03:421122
1123 new_cl_files = []
1124 for line in cl_files_text.splitlines():
1125 if not len(line):
1126 continue
1127 if line.startswith("---"):
1128 break
1129 status = line[:7]
[email protected]e3608df2009-11-10 20:22:571130 filename = line[7:]
1131 new_cl_files.append((status, filename))
[email protected]bfd09ce2009-08-05 21:17:231132
[email protected]3a174252010-10-29 15:54:511133 if (not len(change_info.GetFiles()) and not change_info.issue and
[email protected]20254fc2011-03-22 18:28:591134 not len(new_description) and not new_cl_files):
[email protected]bfd09ce2009-08-05 21:17:231135 ErrorExit("Empty changelist not saved")
1136
[email protected]17f59f22009-06-12 13:27:241137 change_info._files = new_cl_files
[email protected]fb2b8eb2009-04-23 21:03:421138 change_info.Save()
[email protected]53bcf152009-11-13 21:04:101139 if svn_info.get('URL', '').startswith('http:'):
1140 Warn("WARNING: Creating CL in a read-only checkout. You will not be "
1141 "able to commit it!")
1142
[email protected]fb2b8eb2009-04-23 21:03:421143 print change_info.name + " changelist saved."
1144 if change_info.MissingTests():
1145 Warn("WARNING: " + MISSING_TEST_MSG)
1146
[email protected]ea452b32009-11-22 20:04:311147 # Update the Rietveld issue.
1148 if change_info.issue and change_info.NeedsUpload():
1149 change_info.UpdateRietveldDescription()
1150 change_info.needs_upload = False
1151 change_info.Save()
[email protected]4c22d722010-05-14 19:01:221152 return 0
[email protected]ea452b32009-11-22 20:04:311153
1154
[email protected]62fd6932010-05-27 13:13:231155@need_change_and_args
1156def CMDlint(change_info, args):
1157 """Runs cpplint.py on all the files in the change list.
1158
1159 Checks all the files in the changelist for possible style violations.
1160 """
[email protected]fb2b8eb2009-04-23 21:03:421161 try:
1162 import cpplint
[email protected]8b8d8be2011-09-08 15:34:451163 import cpplint_chromium
[email protected]fb2b8eb2009-04-23 21:03:421164 except ImportError:
1165 ErrorExit("You need to install cpplint.py to lint C++ files.")
[email protected]fb2b8eb2009-04-23 21:03:421166 # Change the current working directory before calling lint so that it
1167 # shows the correct base.
1168 previous_cwd = os.getcwd()
[email protected]17f59f22009-06-12 13:27:241169 os.chdir(change_info.GetLocalRoot())
[email protected]fb2b8eb2009-04-23 21:03:421170 # Process cpplints arguments if any.
[email protected]17f59f22009-06-12 13:27:241171 filenames = cpplint.ParseArguments(args + change_info.GetFileNames())
[email protected]fb2b8eb2009-04-23 21:03:421172
[email protected]bb816382009-10-29 01:38:021173 white_list = GetCodeReviewSetting("LINT_REGEX")
1174 if not white_list:
[email protected]e72bb632009-10-29 20:15:481175 white_list = DEFAULT_LINT_REGEX
[email protected]bb816382009-10-29 01:38:021176 white_regex = re.compile(white_list)
1177 black_list = GetCodeReviewSetting("LINT_IGNORE_REGEX")
1178 if not black_list:
[email protected]e72bb632009-10-29 20:15:481179 black_list = DEFAULT_LINT_IGNORE_REGEX
[email protected]bb816382009-10-29 01:38:021180 black_regex = re.compile(black_list)
[email protected]8b8d8be2011-09-08 15:34:451181 extra_check_functions = [cpplint_chromium.CheckPointerDeclarationWhitespace]
[email protected]b17b55b2010-11-03 14:42:371182 # Access to a protected member _XX of a client class
1183 # pylint: disable=W0212
[email protected]e3608df2009-11-10 20:22:571184 for filename in filenames:
1185 if white_regex.match(filename):
1186 if black_regex.match(filename):
1187 print "Ignoring file %s" % filename
[email protected]fb2b8eb2009-04-23 21:03:421188 else:
[email protected]8b8d8be2011-09-08 15:34:451189 cpplint.ProcessFile(filename, cpplint._cpplint_state.verbose_level,
1190 extra_check_functions)
[email protected]bb816382009-10-29 01:38:021191 else:
[email protected]e3608df2009-11-10 20:22:571192 print "Skipping file %s" % filename
[email protected]fb2b8eb2009-04-23 21:03:421193
1194 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count
1195 os.chdir(previous_cwd)
[email protected]4c22d722010-05-14 19:01:221196 return 1
[email protected]fb2b8eb2009-04-23 21:03:421197
1198
[email protected]b0dfd352009-06-10 14:12:541199def DoPresubmitChecks(change_info, committing, may_prompt):
[email protected]fb2b8eb2009-04-23 21:03:421200 """Imports presubmit, then calls presubmit.DoPresubmitChecks."""
[email protected]0ff1fab2009-05-22 13:08:151201 root_presubmit = GetCachedFile('PRESUBMIT.py', use_root=True)
[email protected]2e501802009-06-12 22:00:411202 change = presubmit_support.SvnChange(change_info.name,
1203 change_info.description,
1204 change_info.GetLocalRoot(),
1205 change_info.GetFiles(),
1206 change_info.issue,
[email protected]58407af2011-04-12 23:15:571207 change_info.patchset,
1208 None)
[email protected]cab38e92011-04-09 00:30:511209 output = presubmit_support.DoPresubmitChecks(
1210 change=change,
1211 committing=committing,
1212 verbose=False,
1213 output_stream=sys.stdout,
1214 input_stream=sys.stdin,
1215 default_presubmit=root_presubmit,
1216 may_prompt=may_prompt,
[email protected]239f4112011-06-03 20:08:231217 rietveld_obj=change_info.RpcServer())
[email protected]5ac21012011-03-16 02:58:251218 if not output.should_continue() and may_prompt:
1219 # TODO(dpranke): move into DoPresubmitChecks(), unify cmd line args.
[email protected]fb2b8eb2009-04-23 21:03:421220 print "\nPresubmit errors, can't continue (use --no_presubmit to bypass)"
[email protected]5ac21012011-03-16 02:58:251221
[email protected]30becc22011-03-17 01:21:221222 return output
[email protected]fb2b8eb2009-04-23 21:03:421223
1224
[email protected]62fd6932010-05-27 13:13:231225@no_args
1226def CMDchanges():
[email protected]4c22d722010-05-14 19:01:221227 """Lists all the changelists and their files."""
[email protected]fb2b8eb2009-04-23 21:03:421228 for cl in GetCLs():
[email protected]8d5c9a52009-06-12 15:59:081229 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
[email protected]fb2b8eb2009-04-23 21:03:421230 print "\n--- Changelist " + change_info.name + ":"
[email protected]e3608df2009-11-10 20:22:571231 for filename in change_info.GetFiles():
1232 print "".join(filename)
[email protected]4c22d722010-05-14 19:01:221233 return 0
[email protected]fb2b8eb2009-04-23 21:03:421234
1235
[email protected]62fd6932010-05-27 13:13:231236@no_args
1237def CMDdeleteempties():
[email protected]bfd09ce2009-08-05 21:17:231238 """Delete all changelists that have no files."""
1239 print "\n--- Deleting:"
1240 for cl in GetCLs():
1241 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
[email protected]3a174252010-10-29 15:54:511242 if not len(change_info.GetFiles()):
[email protected]bfd09ce2009-08-05 21:17:231243 print change_info.name
1244 change_info.Delete()
[email protected]4c22d722010-05-14 19:01:221245 return 0
1246
1247
[email protected]62fd6932010-05-27 13:13:231248@no_args
1249def CMDnothave():
[email protected]4c22d722010-05-14 19:01:221250 """Lists files unknown to Subversion."""
[email protected]62fd6932010-05-27 13:13:231251 for filename in UnknownFiles():
[email protected]4c22d722010-05-14 19:01:221252 print "? " + "".join(filename)
1253 return 0
1254
1255
[email protected]62fd6932010-05-27 13:13:231256@attrs(usage='<svn options>')
[email protected]35fe9ad2010-05-25 23:59:541257def CMDdiff(args):
[email protected]62fd6932010-05-27 13:13:231258 """Diffs all files in the changelist or all files that aren't in a CL."""
[email protected]35fe9ad2010-05-25 23:59:541259 files = None
1260 if args:
[email protected]62fd6932010-05-27 13:13:231261 change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
[email protected]35fe9ad2010-05-25 23:59:541262 files = change_info.GetFileNames()
1263 else:
[email protected]707c1482010-06-02 19:52:421264 files = [f[1] for f in GetFilesNotInCL()]
[email protected]38729702010-06-01 23:42:031265
1266 root = GetRepositoryRoot()
1267 cmd = ['svn', 'diff']
1268 cmd.extend([os.path.join(root, x) for x in files])
1269 cmd.extend(args)
[email protected]97335082011-10-20 15:00:161270 return RunShellWithReturnCode(cmd, print_output=True)[1]
[email protected]4c22d722010-05-14 19:01:221271
1272
[email protected]62fd6932010-05-27 13:13:231273@no_args
1274def CMDsettings():
1275 """Prints code review settings for this checkout."""
[email protected]4c22d722010-05-14 19:01:221276 # Force load settings
[email protected]6e29d572010-06-04 17:32:201277 GetCodeReviewSetting("UNKNOWN")
[email protected]4c22d722010-05-14 19:01:221278 del CODEREVIEW_SETTINGS['__just_initialized']
1279 print '\n'.join(("%s: %s" % (str(k), str(v))
1280 for (k,v) in CODEREVIEW_SETTINGS.iteritems()))
1281 return 0
1282
1283
[email protected]35fe9ad2010-05-25 23:59:541284@need_change
1285def CMDdescription(change_info):
[email protected]4c22d722010-05-14 19:01:221286 """Prints the description of the specified change to stdout."""
[email protected]4c22d722010-05-14 19:01:221287 print change_info.description
1288 return 0
1289
1290
[email protected]79b7ef02010-11-01 13:25:131291def CMDdelete(args):
[email protected]4c22d722010-05-14 19:01:221292 """Deletes a changelist."""
[email protected]79b7ef02010-11-01 13:25:131293 if not len(args) == 1:
1294 ErrorExit('You need to pass a change list name')
[email protected]a2d7edf2011-04-04 17:53:451295 filepath = GetChangelistInfoFile(args[0])
1296 if not os.path.isfile(filepath):
1297 ErrorExit('You need to pass a valid change list name')
1298 os.remove(filepath)
[email protected]4c22d722010-05-14 19:01:221299 return 0
1300
1301
[email protected]35fe9ad2010-05-25 23:59:541302def CMDtry(args):
[email protected]62fd6932010-05-27 13:13:231303 """Sends the change to the tryserver to do a test run on your code.
[email protected]4c22d722010-05-14 19:01:221304
1305 To send multiple changes as one path, use a comma-separated list of
1306 changenames. Use 'gcl help try' for more information!"""
1307 # When the change contains no file, send the "changename" positional
1308 # argument to trychange.py.
[email protected]35fe9ad2010-05-25 23:59:541309 # When the command is 'try' and --patchset is used, the patch to try
1310 # is on the Rietveld server.
1311 if not args:
1312 ErrorExit("You need to pass a change list name")
1313 if args[0].find(',') != -1:
1314 change_info = LoadChangelistInfoForMultiple(args[0], GetRepositoryRoot(),
1315 True, True)
1316 else:
1317 change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(),
[email protected]53790112011-11-29 20:36:161318 True, True)
[email protected]4c22d722010-05-14 19:01:221319 if change_info.GetFiles():
[email protected]35fe9ad2010-05-25 23:59:541320 args = args[1:]
[email protected]4c22d722010-05-14 19:01:221321 else:
1322 change_info = None
[email protected]35fe9ad2010-05-25 23:59:541323 return TryChange(change_info, args, swallow_exception=False)
[email protected]4c22d722010-05-14 19:01:221324
1325
[email protected]62fd6932010-05-27 13:13:231326@attrs(usage='<old-name> <new-name>')
[email protected]35fe9ad2010-05-25 23:59:541327def CMDrename(args):
[email protected]4c22d722010-05-14 19:01:221328 """Renames an existing change."""
[email protected]35fe9ad2010-05-25 23:59:541329 if len(args) != 2:
[email protected]4c22d722010-05-14 19:01:221330 ErrorExit("Usage: gcl rename <old-name> <new-name>.")
[email protected]35fe9ad2010-05-25 23:59:541331 src, dst = args
[email protected]4c22d722010-05-14 19:01:221332 src_file = GetChangelistInfoFile(src)
1333 if not os.path.isfile(src_file):
1334 ErrorExit("Change '%s' does not exist." % src)
1335 dst_file = GetChangelistInfoFile(dst)
1336 if os.path.isfile(dst_file):
1337 ErrorExit("Change '%s' already exists; pick a new name." % dst)
1338 os.rename(src_file, dst_file)
1339 print "Change '%s' renamed '%s'." % (src, dst)
1340 return 0
[email protected]bfd09ce2009-08-05 21:17:231341
1342
[email protected]35fe9ad2010-05-25 23:59:541343def CMDpassthru(args):
[email protected]62fd6932010-05-27 13:13:231344 """Everything else that is passed into gcl we redirect to svn.
1345
1346 It assumes a change list name is passed and is converted with the files names.
1347 """
[email protected]f639e632011-10-28 00:25:571348 if not args or len(args) < 2:
1349 ErrorExit("You need to pass a change list name for this svn fall-through "
1350 "command")
[email protected]3f49e2b2011-10-27 15:45:331351 cl_name = args[1]
[email protected]35fe9ad2010-05-25 23:59:541352 args = ["svn", args[0]]
1353 if len(args) > 1:
1354 root = GetRepositoryRoot()
[email protected]3f49e2b2011-10-27 15:45:331355 change_info = ChangeInfo.Load(cl_name, root, True, True)
[email protected]35fe9ad2010-05-25 23:59:541356 args.extend([os.path.join(root, x) for x in change_info.GetFileNames()])
[email protected]97335082011-10-20 15:00:161357 return RunShellWithReturnCode(args, print_output=True)[1]
[email protected]35fe9ad2010-05-25 23:59:541358
1359
[email protected]62fd6932010-05-27 13:13:231360def Command(name):
1361 return getattr(sys.modules[__name__], 'CMD' + name, None)
1362
1363
1364def GenUsage(command):
1365 """Modify an OptParse object with the function's documentation."""
1366 obj = Command(command)
1367 display = command
1368 more = getattr(obj, 'usage', '')
1369 if command == 'help':
1370 display = '<command>'
[email protected]3a174252010-10-29 15:54:511371 need_change_val = ''
[email protected]62fd6932010-05-27 13:13:231372 if getattr(obj, 'need_change', None):
[email protected]3a174252010-10-29 15:54:511373 need_change_val = ' <change_list>'
[email protected]62fd6932010-05-27 13:13:231374 options = ' [options]'
1375 if getattr(obj, 'no_args', None):
1376 options = ''
[email protected]3a174252010-10-29 15:54:511377 res = 'Usage: gcl %s%s%s %s\n\n' % (display, need_change_val, options, more)
[email protected]62fd6932010-05-27 13:13:231378 res += re.sub('\n ', '\n', obj.__doc__)
1379 return res
1380
1381
1382def CMDhelp(args):
1383 """Prints this help or help for the given command."""
1384 if args and 'CMD' + args[0] in dir(sys.modules[__name__]):
1385 print GenUsage(args[0])
1386
1387 # These commands defer to external tools so give this info too.
1388 if args[0] == 'try':
1389 TryChange(None, ['--help'], swallow_exception=False)
1390 if args[0] == 'upload':
1391 upload.RealMain(['upload.py', '--help'])
1392 return 0
1393
1394 print GenUsage('help')
1395 print sys.modules[__name__].__doc__
1396 print 'version ' + __version__ + '\n'
1397
1398 print('Commands are:\n' + '\n'.join([
1399 ' %-12s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1400 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1401 return 0
1402
1403
[email protected]35fe9ad2010-05-25 23:59:541404def main(argv):
[email protected]c3a15a22010-11-20 03:12:271405 if sys.hexversion < 0x02050000:
1406 print >> sys.stderr, (
1407 '\nYour python version is unsupported, please upgrade.\n')
[email protected]c68f9cb2010-06-17 20:34:181408 if not argv:
1409 argv = ['help']
1410 command = Command(argv[0])
1411 # Help can be run from anywhere.
1412 if command == CMDhelp:
1413 return command(argv[1:])
1414
[email protected]a05be0b2009-06-30 19:13:021415 try:
[email protected]62fd6932010-05-27 13:13:231416 GetRepositoryRoot()
[email protected]31cb48a2011-04-04 18:01:361417 except (gclient_utils.Error, subprocess2.CalledProcessError):
[email protected]58c19382010-09-22 19:53:591418 print >> sys.stderr, 'To use gcl, you need to be in a subversion checkout.'
[email protected]62fd6932010-05-27 13:13:231419 return 1
1420
1421 # Create the directories where we store information about changelists if it
1422 # doesn't exist.
[email protected]807c4462010-07-10 00:45:281423 try:
1424 if not os.path.exists(GetInfoDir()):
1425 os.mkdir(GetInfoDir())
1426 if not os.path.exists(GetChangesDir()):
1427 os.mkdir(GetChangesDir())
1428 if not os.path.exists(GetCacheDir()):
1429 os.mkdir(GetCacheDir())
[email protected]fb2b8eb2009-04-23 21:03:421430
[email protected]807c4462010-07-10 00:45:281431 if command:
1432 return command(argv[1:])
1433 # Unknown command, try to pass that to svn
1434 return CMDpassthru(argv)
[email protected]31cb48a2011-04-04 18:01:361435 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
[email protected]58c19382010-09-22 19:53:591436 print >> sys.stderr, 'Got an exception'
1437 print >> sys.stderr, str(e)
1438 return 1
[email protected]42c7f662010-10-06 23:52:461439 except upload.ClientLoginError, e:
1440 print >> sys.stderr, 'Got an exception logging in to Rietveld'
1441 print >> sys.stderr, str(e)
[email protected]58c19382010-09-22 19:53:591442 except urllib2.HTTPError, e:
1443 if e.code != 500:
1444 raise
1445 print >> sys.stderr, (
1446 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
[email protected]7633e472010-09-22 20:03:141447 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))
[email protected]58c19382010-09-22 19:53:591448 return 1
1449
[email protected]fb2b8eb2009-04-23 21:03:421450
1451if __name__ == "__main__":
[email protected]35625c72011-03-23 17:34:021452 fix_encoding.fix_encoding()
[email protected]35fe9ad2010-05-25 23:59:541453 sys.exit(main(sys.argv[1:]))