blob: 413e2bb6d361918276ff823db8d92a561e12b55f [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
16import subprocess
17import sys
18import tempfile
[email protected]2f6a0d82010-05-12 00:03:3019import time
[email protected]ba551772010-02-03 18:21:4220from third_party import upload
[email protected]fb2b8eb2009-04-23 21:03:4221import urllib2
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]cb2985f2010-11-03 14:08:3133import breakpad # pylint: disable=W0611
[email protected]ada4c652009-12-03 15:32:0134
[email protected]46a94102009-05-12 20:32:4335# gcl now depends on gclient.
[email protected]5aeb7dd2009-11-17 18:09:0136from scm import SVN
[email protected]30becc22011-03-17 01:21:2237
[email protected]35625c72011-03-23 17:34:0238import fix_encoding
[email protected]5f3eee32009-09-17 00:34:3039import gclient_utils
[email protected]30becc22011-03-17 01:21:2240import presubmit_support
[email protected]cab38e92011-04-09 00:30:5141import rietveld
[email protected]31cb48a2011-04-04 18:01:3642import subprocess2
[email protected]c1675e22009-04-27 20:30:4843
[email protected]cab38e92011-04-09 00:30:5144__version__ = '1.2.1'
[email protected]c1675e22009-04-27 20:30:4845
46
[email protected]fb2b8eb2009-04-23 21:03:4247CODEREVIEW_SETTINGS = {
[email protected]b8260242010-08-19 17:03:1648 # To make gcl send reviews to a server, check in a file named
[email protected]172b6e72010-01-26 00:35:0349 # "codereview.settings" (see |CODEREVIEW_SETTINGS_FILE| below) to your
50 # project's base directory and add the following line to codereview.settings:
51 # CODE_REVIEW_SERVER: codereview.yourserver.org
[email protected]fb2b8eb2009-04-23 21:03:4252}
53
[email protected]fb2b8eb2009-04-23 21:03:4254# globals that store the root of the current repository and the directory where
55# we store information about changelists.
[email protected]98fc2b92009-05-21 14:11:5156REPOSITORY_ROOT = ""
[email protected]fb2b8eb2009-04-23 21:03:4257
58# Filename where we store repository specific information for gcl.
59CODEREVIEW_SETTINGS_FILE = "codereview.settings"
[email protected]b8260242010-08-19 17:03:1660CODEREVIEW_SETTINGS_FILE_NOT_FOUND = (
61 'No %s file found. Please add one.' % CODEREVIEW_SETTINGS_FILE)
[email protected]fb2b8eb2009-04-23 21:03:4262
63# Warning message when the change appears to be missing tests.
64MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!"
65
[email protected]98fc2b92009-05-21 14:11:5166# Global cache of files cached in GetCacheDir().
67FILES_CACHE = {}
[email protected]fb2b8eb2009-04-23 21:03:4268
[email protected]4c22d722010-05-14 19:01:2269# Valid extensions for files we want to lint.
70DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
71DEFAULT_LINT_IGNORE_REGEX = r"$^"
72
[email protected]30becc22011-03-17 01:21:2273REVIEWERS_REGEX = r'\s*R=(.+)'
[email protected]4c22d722010-05-14 19:01:2274
[email protected]e5299012010-04-07 18:02:2675def CheckHomeForFile(filename):
76 """Checks the users home dir for the existence of the given file. Returns
77 the path to the file if it's there, or None if it is not.
78 """
79 home_vars = ['HOME']
80 if sys.platform in ('cygwin', 'win32'):
81 home_vars.append('USERPROFILE')
82 for home_var in home_vars:
83 home = os.getenv(home_var)
84 if home != None:
85 full_path = os.path.join(home, filename)
86 if os.path.exists(full_path):
87 return full_path
88 return None
[email protected]fb2b8eb2009-04-23 21:03:4289
[email protected]35fe9ad2010-05-25 23:59:5490
[email protected]62fd6932010-05-27 13:13:2391def UnknownFiles():
92 """Runs svn status and returns unknown files."""
93 return [item[1] for item in SVN.CaptureStatus([]) if item[0][0] == '?']
[email protected]207fdf32009-04-28 19:57:0194
95
[email protected]fb2b8eb2009-04-23 21:03:4296def GetRepositoryRoot():
97 """Returns the top level directory of the current repository.
98
99 The directory is returned as an absolute path.
100 """
[email protected]98fc2b92009-05-21 14:11:51101 global REPOSITORY_ROOT
102 if not REPOSITORY_ROOT:
[email protected]94b1ee92009-12-19 20:27:20103 REPOSITORY_ROOT = SVN.GetCheckoutRoot(os.getcwd())
104 if not REPOSITORY_ROOT:
[email protected]5f3eee32009-09-17 00:34:30105 raise gclient_utils.Error("gcl run outside of repository")
[email protected]98fc2b92009-05-21 14:11:51106 return REPOSITORY_ROOT
[email protected]fb2b8eb2009-04-23 21:03:42107
108
109def GetInfoDir():
110 """Returns the directory where gcl info files are stored."""
[email protected]374d65e2009-05-21 14:00:52111 return os.path.join(GetRepositoryRoot(), '.svn', 'gcl_info')
112
113
114def GetChangesDir():
115 """Returns the directory where gcl change files are stored."""
116 return os.path.join(GetInfoDir(), 'changes')
[email protected]fb2b8eb2009-04-23 21:03:42117
118
[email protected]98fc2b92009-05-21 14:11:51119def GetCacheDir():
120 """Returns the directory where gcl change files are stored."""
121 return os.path.join(GetInfoDir(), 'cache')
122
123
124def GetCachedFile(filename, max_age=60*60*24*3, use_root=False):
125 """Retrieves a file from the repository and caches it in GetCacheDir() for
126 max_age seconds.
127
128 use_root: If False, look up the arborescence for the first match, otherwise go
129 directory to the root repository.
130
131 Note: The cache will be inconsistent if the same file is retrieved with both
[email protected]bb816382009-10-29 01:38:02132 use_root=True and use_root=False. Don't be stupid.
[email protected]98fc2b92009-05-21 14:11:51133 """
[email protected]98fc2b92009-05-21 14:11:51134 if filename not in FILES_CACHE:
135 # Don't try to look up twice.
136 FILES_CACHE[filename] = None
[email protected]9b613272009-04-24 01:28:28137 # First we check if we have a cached version.
[email protected]a05be0b2009-06-30 19:13:02138 try:
139 cached_file = os.path.join(GetCacheDir(), filename)
[email protected]31cb48a2011-04-04 18:01:36140 except (gclient_utils.Error, subprocess2.CalledProcessError):
[email protected]a05be0b2009-06-30 19:13:02141 return None
[email protected]98fc2b92009-05-21 14:11:51142 if (not os.path.exists(cached_file) or
[email protected]2f6a0d82010-05-12 00:03:30143 (time.time() - os.stat(cached_file).st_mtime) > max_age):
[email protected]b3b494f2010-08-31 20:40:08144 dir_info = SVN.CaptureInfo('.')
145 repo_root = dir_info['Repository Root']
[email protected]98fc2b92009-05-21 14:11:51146 if use_root:
147 url_path = repo_root
148 else:
[email protected]b3b494f2010-08-31 20:40:08149 url_path = dir_info['URL']
[email protected]9b613272009-04-24 01:28:28150 while True:
[email protected]fa44e4a2009-12-03 01:41:13151 # Look in the repository at the current level for the file.
[email protected]b6ee1672010-08-19 17:06:07152 for _ in range(5):
[email protected]b3b494f2010-08-31 20:40:08153 content = None
[email protected]b6ee1672010-08-19 17:06:07154 try:
155 # Take advantage of the fact that svn won't output to stderr in case
156 # of success but will do in case of failure so don't mind putting
157 # stderr into content_array.
158 content_array = []
[email protected]b3b494f2010-08-31 20:40:08159 svn_path = url_path + '/' + filename
[email protected]17368002010-09-01 23:36:32160 args = ['svn', 'cat', svn_path]
[email protected]02913a52010-08-25 20:50:50161 if sys.platform != 'darwin':
162 # MacOSX 10.5.2 has a bug with svn 1.4.4 that will trigger the
163 # 'Can\'t get username or password' and can be fixed easily.
164 # The fix doesn't work if the user upgraded to svn 1.6.x. Bleh.
165 # I don't have time to fix their broken stuff.
166 args.append('--non-interactive')
[email protected]17d01792010-09-01 18:07:10167 gclient_utils.CheckCallAndFilter(
168 args, cwd='.', filter_fn=content_array.append)
[email protected]b6ee1672010-08-19 17:06:07169 # Exit the loop if the file was found. Override content.
170 content = '\n'.join(content_array)
171 break
[email protected]31cb48a2011-04-04 18:01:36172 except (gclient_utils.Error, subprocess2.CalledProcessError):
[email protected]b6ee1672010-08-19 17:06:07173 if content_array[0].startswith(
174 'svn: Can\'t get username or password'):
175 ErrorExit('Your svn credentials expired. Please run svn update '
176 'to fix the cached credentials')
[email protected]3c842982010-08-20 17:26:49177 if content_array[0].startswith('svn: Can\'t get password'):
178 ErrorExit('If are using a Mac and svn --version shows 1.4.x, '
179 'please hack gcl.py to remove --non-interactive usage, it\'s'
180 'a bug on your installed copy')
[email protected]dcd15222010-12-21 22:43:19181 if (content_array[0].startswith('svn: File not found:') or
182 content_array[0].endswith('path not found')):
183 break
184 # Otherwise, fall through to trying again.
[email protected]b6ee1672010-08-19 17:06:07185 if content:
[email protected]9b613272009-04-24 01:28:28186 break
[email protected]9b613272009-04-24 01:28:28187 if url_path == repo_root:
188 # Reached the root. Abandoning search.
[email protected]98fc2b92009-05-21 14:11:51189 break
[email protected]9b613272009-04-24 01:28:28190 # Go up one level to try again.
191 url_path = os.path.dirname(url_path)
[email protected]b3b494f2010-08-31 20:40:08192 if content is not None or filename != CODEREVIEW_SETTINGS_FILE:
193 # Write a cached version even if there isn't a file, so we don't try to
194 # fetch it each time. codereview.settings must always be present so do
195 # not cache negative.
196 gclient_utils.FileWrite(cached_file, content or '')
[email protected]98fc2b92009-05-21 14:11:51197 else:
[email protected]0fca4f32009-12-18 15:14:34198 content = gclient_utils.FileRead(cached_file, 'r')
[email protected]98fc2b92009-05-21 14:11:51199 # Keep the content cached in memory.
200 FILES_CACHE[filename] = content
201 return FILES_CACHE[filename]
[email protected]9b613272009-04-24 01:28:28202
[email protected]98fc2b92009-05-21 14:11:51203
204def GetCodeReviewSetting(key):
205 """Returns a value for the given key for this repository."""
206 # Use '__just_initialized' as a flag to determine if the settings were
207 # already initialized.
[email protected]98fc2b92009-05-21 14:11:51208 if '__just_initialized' not in CODEREVIEW_SETTINGS:
[email protected]b0442182009-06-05 14:20:47209 settings_file = GetCachedFile(CODEREVIEW_SETTINGS_FILE)
210 if settings_file:
211 for line in settings_file.splitlines():
[email protected]807c4462010-07-10 00:45:28212 if not line or line.startswith('#'):
[email protected]b0442182009-06-05 14:20:47213 continue
[email protected]807c4462010-07-10 00:45:28214 if not ':' in line:
215 raise gclient_utils.Error(
216 '%s is invalid, please fix. It\'s content:\n\n%s' %
217 (CODEREVIEW_SETTINGS_FILE, settings_file))
[email protected]dd218e52010-08-23 13:02:41218 k, v = line.split(':', 1)
219 CODEREVIEW_SETTINGS[k.strip()] = v.strip()
[email protected]98fc2b92009-05-21 14:11:51220 CODEREVIEW_SETTINGS.setdefault('__just_initialized', None)
[email protected]fb2b8eb2009-04-23 21:03:42221 return CODEREVIEW_SETTINGS.get(key, "")
222
223
[email protected]fb2b8eb2009-04-23 21:03:42224def Warn(msg):
[email protected]6e29d572010-06-04 17:32:20225 print >> sys.stderr, msg
[email protected]223b7192010-06-04 18:52:58226
227
228def ErrorExit(msg):
229 print >> sys.stderr, msg
230 sys.exit(1)
[email protected]fb2b8eb2009-04-23 21:03:42231
232
233def RunShellWithReturnCode(command, print_output=False):
234 """Executes a command and returns the output and the return code."""
[email protected]3a292682010-08-23 18:54:55235 p = gclient_utils.Popen(command, stdout=subprocess.PIPE,
236 stderr=subprocess.STDOUT, universal_newlines=True)
[email protected]fb2b8eb2009-04-23 21:03:42237 if print_output:
238 output_array = []
239 while True:
240 line = p.stdout.readline()
241 if not line:
242 break
243 if print_output:
244 print line.strip('\n')
245 output_array.append(line)
246 output = "".join(output_array)
247 else:
248 output = p.stdout.read()
249 p.wait()
250 p.stdout.close()
251 return output, p.returncode
252
253
254def RunShell(command, print_output=False):
255 """Executes a command and returns the output."""
256 return RunShellWithReturnCode(command, print_output)[0]
257
258
[email protected]51ee0072009-06-08 19:20:05259def FilterFlag(args, flag):
260 """Returns True if the flag is present in args list.
261
262 The flag is removed from args if present.
263 """
264 if flag in args:
265 args.remove(flag)
266 return True
267 return False
268
269
[email protected]be0d1ca2009-05-12 19:23:02270class ChangeInfo(object):
[email protected]fb2b8eb2009-04-23 21:03:42271 """Holds information about a changelist.
272
[email protected]32ba2602009-06-06 18:44:48273 name: change name.
274 issue: the Rietveld issue number or 0 if it hasn't been uploaded yet.
275 patchset: the Rietveld latest patchset number or 0.
[email protected]fb2b8eb2009-04-23 21:03:42276 description: the description.
277 files: a list of 2 tuple containing (status, filename) of changed files,
278 with paths being relative to the top repository directory.
[email protected]8d5c9a52009-06-12 15:59:08279 local_root: Local root directory
[email protected]bf1fdca2010-11-01 18:05:36280 rietveld: rietveld server for this change
[email protected]fb2b8eb2009-04-23 21:03:42281 """
[email protected]bf1fdca2010-11-01 18:05:36282 # Kept for unit test support. This is for the old format, it's deprecated.
[email protected]32ba2602009-06-06 18:44:48283 _SEPARATOR = "\n-----\n"
[email protected]32ba2602009-06-06 18:44:48284
[email protected]ea452b32009-11-22 20:04:31285 def __init__(self, name, issue, patchset, description, files, local_root,
[email protected]6493ed12011-04-14 13:51:00286 rietveld_url, needs_upload):
[email protected]fb2b8eb2009-04-23 21:03:42287 self.name = name
[email protected]32ba2602009-06-06 18:44:48288 self.issue = int(issue)
289 self.patchset = int(patchset)
[email protected]20254fc2011-03-22 18:28:59290 self._description = None
291 self._subject = None
292 self._reviewers = None
[email protected]30becc22011-03-17 01:21:22293 self._set_description(description)
[email protected]be0d1ca2009-05-12 19:23:02294 if files is None:
295 files = []
[email protected]17f59f22009-06-12 13:27:24296 self._files = files
[email protected]fb2b8eb2009-04-23 21:03:42297 self.patch = None
[email protected]8d5c9a52009-06-12 15:59:08298 self._local_root = local_root
[email protected]ea452b32009-11-22 20:04:31299 self.needs_upload = needs_upload
[email protected]cab38e92011-04-09 00:30:51300 self.rietveld = rietveld_url
[email protected]bf1fdca2010-11-01 18:05:36301 if not self.rietveld:
302 # Set the default value.
303 self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER')
[email protected]cab38e92011-04-09 00:30:51304 self._rpc_server = None
[email protected]ea452b32009-11-22 20:04:31305
[email protected]30becc22011-03-17 01:21:22306 def _get_description(self):
[email protected]20254fc2011-03-22 18:28:59307 return self._description
[email protected]30becc22011-03-17 01:21:22308
309 def _set_description(self, description):
[email protected]20254fc2011-03-22 18:28:59310 # TODO(dpranke): Cloned from git_cl.py. These should be shared.
311 if not description:
312 self._description = description
313 return
314
315 parsed_lines = []
316 reviewers_re = re.compile(REVIEWERS_REGEX)
317 reviewers = ''
318 subject = ''
319 for l in description.splitlines():
320 if not subject:
321 subject = l
322 matched_reviewers = reviewers_re.match(l)
323 if matched_reviewers:
324 reviewers = matched_reviewers.group(1).split(',')
325 parsed_lines.append(l)
326
327 if len(subject) > 100:
328 subject = subject[:97] + '...'
329
330 self._subject = subject
331 self._reviewers = reviewers
332 self._description = '\n'.join(parsed_lines)
[email protected]30becc22011-03-17 01:21:22333
334 description = property(_get_description, _set_description)
335
336 @property
337 def reviewers(self):
[email protected]20254fc2011-03-22 18:28:59338 return self._reviewers
[email protected]30becc22011-03-17 01:21:22339
340 @property
341 def subject(self):
[email protected]20254fc2011-03-22 18:28:59342 return self._subject
[email protected]30becc22011-03-17 01:21:22343
[email protected]ea452b32009-11-22 20:04:31344 def NeedsUpload(self):
345 return self.needs_upload
[email protected]fb2b8eb2009-04-23 21:03:42346
[email protected]17f59f22009-06-12 13:27:24347 def GetFileNames(self):
348 """Returns the list of file names included in this change."""
[email protected]e3608df2009-11-10 20:22:57349 return [f[1] for f in self._files]
[email protected]17f59f22009-06-12 13:27:24350
351 def GetFiles(self):
352 """Returns the list of files included in this change with their status."""
353 return self._files
354
355 def GetLocalRoot(self):
356 """Returns the local repository checkout root directory."""
357 return self._local_root
[email protected]fb2b8eb2009-04-23 21:03:42358
[email protected]f0dfba32009-08-07 22:03:37359 def Exists(self):
360 """Returns True if this change already exists (i.e., is not new)."""
361 return (self.issue or self.description or self._files)
362
[email protected]fb2b8eb2009-04-23 21:03:42363 def _NonDeletedFileList(self):
364 """Returns a list of files in this change, not including deleted files."""
[email protected]e3608df2009-11-10 20:22:57365 return [f[1] for f in self.GetFiles()
366 if not f[0].startswith("D")]
[email protected]fb2b8eb2009-04-23 21:03:42367
368 def _AddedFileList(self):
369 """Returns a list of files added in this change."""
[email protected]e3608df2009-11-10 20:22:57370 return [f[1] for f in self.GetFiles() if f[0].startswith("A")]
[email protected]fb2b8eb2009-04-23 21:03:42371
372 def Save(self):
373 """Writes the changelist information to disk."""
[email protected]fa3843e2010-11-01 13:34:37374 data = json.dumps({
375 'issue': self.issue,
376 'patchset': self.patchset,
377 'needs_upload': self.NeedsUpload(),
378 'files': self.GetFiles(),
[email protected]20254fc2011-03-22 18:28:59379 'description': self.description,
[email protected]bf1fdca2010-11-01 18:05:36380 'rietveld': self.rietveld,
[email protected]fa3843e2010-11-01 13:34:37381 }, sort_keys=True, indent=2)
[email protected]fc83c112009-12-18 15:14:10382 gclient_utils.FileWrite(GetChangelistInfoFile(self.name), data)
[email protected]fb2b8eb2009-04-23 21:03:42383
384 def Delete(self):
385 """Removes the changelist information from disk."""
386 os.remove(GetChangelistInfoFile(self.name))
387
[email protected]cab38e92011-04-09 00:30:51388 def RpcServer(self):
389 if not self._rpc_server:
390 if not self.rietveld:
391 ErrorExit(CODEREVIEW_SETTINGS_FILE_NOT_FOUND)
392 self._rpc_server = rietveld.Rietveld(self.rietveld, None, None)
393 return self._rpc_server
394
[email protected]fb2b8eb2009-04-23 21:03:42395 def CloseIssue(self):
396 """Closes the Rietveld issue for this changelist."""
[email protected]41db6ef2010-11-17 19:42:34397 # Newer versions of Rietveld require us to pass an XSRF token to POST, so
398 # we fetch it from the server.
399 xsrf_token = self.SendToRietveld(
400 '/xsrf_token',
401 extra_headers={'X-Requesting-XSRF-Token': '1'})
402
403 # You cannot close an issue with a GET.
404 # We pass an empty string for the data so it is a POST rather than a GET.
405 data = [("description", self.description),
406 ("xsrf_token", xsrf_token)]
[email protected]fb2b8eb2009-04-23 21:03:42407 ctype, body = upload.EncodeMultipartFormData(data, [])
[email protected]41db6ef2010-11-17 19:42:34408 self.SendToRietveld('/%d/close' % self.issue, payload=body,
409 content_type=ctype)
[email protected]fb2b8eb2009-04-23 21:03:42410
411 def UpdateRietveldDescription(self):
412 """Sets the description for an issue on Rietveld."""
413 data = [("description", self.description),]
414 ctype, body = upload.EncodeMultipartFormData(data, [])
[email protected]41db6ef2010-11-17 19:42:34415 self.SendToRietveld('/%d/description' % self.issue, payload=body,
416 content_type=ctype)
[email protected]bf1fdca2010-11-01 18:05:36417
418 def GetIssueDescription(self):
419 """Returns the issue description from Rietveld."""
420 return self.SendToRietveld('/%d/description' % self.issue)
421
422 def PrimeLint(self):
423 """Do background work on Rietveld to lint the file so that the results are
424 ready when the issue is viewed."""
425 if self.issue and self.patchset:
426 self.SendToRietveld('/lint/issue%s_%s' % (self.issue, self.patchset),
427 timeout=1)
428
[email protected]41db6ef2010-11-17 19:42:34429 def SendToRietveld(self, request_path, timeout=None, **kwargs):
[email protected]bf1fdca2010-11-01 18:05:36430 """Send a POST/GET to Rietveld. Returns the response body."""
[email protected]bf1fdca2010-11-01 18:05:36431 try:
[email protected]cab38e92011-04-09 00:30:51432 return self.RpcServer().Send(request_path, timeout=timeout, **kwargs)
[email protected]bf1fdca2010-11-01 18:05:36433 except urllib2.URLError:
434 if timeout is None:
435 ErrorExit('Error accessing url %s' % request_path)
436 else:
437 return None
[email protected]fb2b8eb2009-04-23 21:03:42438
439 def MissingTests(self):
440 """Returns True if the change looks like it needs unit tests but has none.
441
442 A change needs unit tests if it contains any new source files or methods.
443 """
444 SOURCE_SUFFIXES = [".cc", ".cpp", ".c", ".m", ".mm"]
445 # Ignore third_party entirely.
[email protected]e3608df2009-11-10 20:22:57446 files = [f for f in self._NonDeletedFileList()
447 if f.find("third_party") == -1]
448 added_files = [f for f in self._AddedFileList()
449 if f.find("third_party") == -1]
[email protected]fb2b8eb2009-04-23 21:03:42450
451 # If the change is entirely in third_party, we're done.
452 if len(files) == 0:
453 return False
454
455 # Any new or modified test files?
456 # A test file's name ends with "test.*" or "tests.*".
457 test_files = [test for test in files
458 if os.path.splitext(test)[0].rstrip("s").endswith("test")]
459 if len(test_files) > 0:
460 return False
461
462 # Any new source files?
[email protected]e3608df2009-11-10 20:22:57463 source_files = [item for item in added_files
464 if os.path.splitext(item)[1] in SOURCE_SUFFIXES]
[email protected]fb2b8eb2009-04-23 21:03:42465 if len(source_files) > 0:
466 return True
467
468 # Do the long test, checking the files for new methods.
469 return self._HasNewMethod()
470
471 def _HasNewMethod(self):
472 """Returns True if the changeset contains any new functions, or if a
473 function signature has been changed.
474
475 A function is identified by starting flush left, containing a "(" before
476 the next flush-left line, and either ending with "{" before the next
477 flush-left line or being followed by an unindented "{".
478
479 Currently this returns True for new methods, new static functions, and
480 methods or functions whose signatures have been changed.
481
482 Inline methods added to header files won't be detected by this. That's
483 acceptable for purposes of determining if a unit test is needed, since
484 inline methods should be trivial.
485 """
486 # To check for methods added to source or header files, we need the diffs.
487 # We'll generate them all, since there aren't likely to be many files
488 # apart from source and headers; besides, we'll want them all if we're
489 # uploading anyway.
490 if self.patch is None:
[email protected]17f59f22009-06-12 13:27:24491 self.patch = GenerateDiff(self.GetFileNames())
[email protected]fb2b8eb2009-04-23 21:03:42492
493 definition = ""
494 for line in self.patch.splitlines():
495 if not line.startswith("+"):
496 continue
497 line = line.strip("+").rstrip(" \t")
498 # Skip empty lines, comments, and preprocessor directives.
499 # TODO(pamg): Handle multiline comments if it turns out to be a problem.
500 if line == "" or line.startswith("/") or line.startswith("#"):
501 continue
502
503 # A possible definition ending with "{" is complete, so check it.
504 if definition.endswith("{"):
505 if definition.find("(") != -1:
506 return True
507 definition = ""
508
509 # A { or an indented line, when we're in a definition, continues it.
510 if (definition != "" and
511 (line == "{" or line.startswith(" ") or line.startswith("\t"))):
512 definition += line
513
514 # A flush-left line starts a new possible function definition.
515 elif not line.startswith(" ") and not line.startswith("\t"):
516 definition = line
517
518 return False
519
[email protected]32ba2602009-06-06 18:44:48520 @staticmethod
[email protected]8d5c9a52009-06-12 15:59:08521 def Load(changename, local_root, fail_on_not_found, update_status):
[email protected]32ba2602009-06-06 18:44:48522 """Gets information about a changelist.
[email protected]fb2b8eb2009-04-23 21:03:42523
[email protected]32ba2602009-06-06 18:44:48524 Args:
525 fail_on_not_found: if True, this function will quit the program if the
526 changelist doesn't exist.
527 update_status: if True, the svn status will be updated for all the files
528 and unchanged files will be removed.
529
530 Returns: a ChangeInfo object.
531 """
532 info_file = GetChangelistInfoFile(changename)
533 if not os.path.exists(info_file):
534 if fail_on_not_found:
535 ErrorExit("Changelist " + changename + " not found.")
[email protected]6493ed12011-04-14 13:51:00536 return ChangeInfo(changename, 0, 0, '', None, local_root, None, False)
[email protected]8a8ea022010-11-01 13:27:32537 content = gclient_utils.FileRead(info_file, 'r')
538 save = False
539 try:
[email protected]fa3843e2010-11-01 13:34:37540 values = ChangeInfo._LoadNewFormat(content)
[email protected]8a8ea022010-11-01 13:27:32541 except ValueError:
[email protected]fa3843e2010-11-01 13:34:37542 try:
543 values = ChangeInfo._LoadOldFormat(content)
544 save = True
545 except ValueError:
546 ErrorExit(
547 ('Changelist file %s is corrupt.\n'
548 'Either run "gcl delete %s" or manually edit the file') % (
549 info_file, changename))
[email protected]8a8ea022010-11-01 13:27:32550 files = values['files']
[email protected]32ba2602009-06-06 18:44:48551 if update_status:
[email protected]79b7ef02010-11-01 13:25:13552 for item in files[:]:
[email protected]e3608df2009-11-10 20:22:57553 filename = os.path.join(local_root, item[1])
[email protected]5aeb7dd2009-11-17 18:09:01554 status_result = SVN.CaptureStatus(filename)
[email protected]32ba2602009-06-06 18:44:48555 if not status_result or not status_result[0][0]:
556 # File has been reverted.
557 save = True
[email protected]e3608df2009-11-10 20:22:57558 files.remove(item)
[email protected]32ba2602009-06-06 18:44:48559 continue
560 status = status_result[0][0]
[email protected]e3608df2009-11-10 20:22:57561 if status != item[0]:
[email protected]32ba2602009-06-06 18:44:48562 save = True
[email protected]e3608df2009-11-10 20:22:57563 files[files.index(item)] = (status, item[1])
[email protected]6493ed12011-04-14 13:51:00564 change_info = ChangeInfo(
565 changename,
566 values['issue'],
567 values['patchset'],
568 values['description'],
569 files,
570 local_root,
571 values.get('rietveld'),
572 values['needs_upload'])
[email protected]32ba2602009-06-06 18:44:48573 if save:
574 change_info.Save()
575 return change_info
[email protected]fb2b8eb2009-04-23 21:03:42576
[email protected]8a8ea022010-11-01 13:27:32577 @staticmethod
578 def _LoadOldFormat(content):
[email protected]bf1fdca2010-11-01 18:05:36579 # The info files have the following format:
580 # issue_id, patchset\n (, patchset is optional)
581 # _SEPARATOR\n
582 # filepath1\n
583 # filepath2\n
584 # .
585 # .
586 # filepathn\n
587 # _SEPARATOR\n
588 # description
[email protected]8a8ea022010-11-01 13:27:32589 split_data = content.split(ChangeInfo._SEPARATOR, 2)
590 if len(split_data) != 3:
591 raise ValueError('Bad change format')
592 values = {
593 'issue': 0,
594 'patchset': 0,
595 'needs_upload': False,
596 'files': [],
597 }
598 items = split_data[0].split(', ')
599 if items[0]:
600 values['issue'] = int(items[0])
601 if len(items) > 1:
602 values['patchset'] = int(items[1])
603 if len(items) > 2:
604 values['needs_upload'] = (items[2] == "dirty")
605 for line in split_data[1].splitlines():
606 status = line[:7]
607 filename = line[7:]
608 values['files'].append((status, filename))
609 values['description'] = split_data[2]
610 return values
611
[email protected]fa3843e2010-11-01 13:34:37612 @staticmethod
613 def _LoadNewFormat(content):
614 return json.loads(content)
615
[email protected]fb2b8eb2009-04-23 21:03:42616
617def GetChangelistInfoFile(changename):
618 """Returns the file that stores information about a changelist."""
619 if not changename or re.search(r'[^\w-]', changename):
620 ErrorExit("Invalid changelist name: " + changename)
[email protected]374d65e2009-05-21 14:00:52621 return os.path.join(GetChangesDir(), changename)
[email protected]fb2b8eb2009-04-23 21:03:42622
623
[email protected]8d5c9a52009-06-12 15:59:08624def LoadChangelistInfoForMultiple(changenames, local_root, fail_on_not_found,
625 update_status):
[email protected]fb2b8eb2009-04-23 21:03:42626 """Loads many changes and merge their files list into one pseudo change.
627
628 This is mainly usefull to concatenate many changes into one for a 'gcl try'.
629 """
630 changes = changenames.split(',')
[email protected]6493ed12011-04-14 13:51:00631 aggregate_change_info = ChangeInfo(
632 changenames, 0, 0, '', None, local_root, None, False)
[email protected]fb2b8eb2009-04-23 21:03:42633 for change in changes:
[email protected]6493ed12011-04-14 13:51:00634 aggregate_change_info._files += ChangeInfo.Load(
635 change, local_root, fail_on_not_found, 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,
[email protected]58407af2011-04-12 23:15:571223 change_info.patchset,
1224 None)
[email protected]cab38e92011-04-09 00:30:511225 output = presubmit_support.DoPresubmitChecks(
1226 change=change,
1227 committing=committing,
1228 verbose=False,
1229 output_stream=sys.stdout,
1230 input_stream=sys.stdin,
1231 default_presubmit=root_presubmit,
1232 may_prompt=may_prompt,
1233 tbr=False,
1234 rietveld=change_info.RpcServer())
[email protected]5ac21012011-03-16 02:58:251235 if not output.should_continue() and may_prompt:
1236 # TODO(dpranke): move into DoPresubmitChecks(), unify cmd line args.
[email protected]fb2b8eb2009-04-23 21:03:421237 print "\nPresubmit errors, can't continue (use --no_presubmit to bypass)"
[email protected]5ac21012011-03-16 02:58:251238
[email protected]30becc22011-03-17 01:21:221239 return output
[email protected]fb2b8eb2009-04-23 21:03:421240
1241
[email protected]62fd6932010-05-27 13:13:231242@no_args
1243def CMDchanges():
[email protected]4c22d722010-05-14 19:01:221244 """Lists all the changelists and their files."""
[email protected]fb2b8eb2009-04-23 21:03:421245 for cl in GetCLs():
[email protected]8d5c9a52009-06-12 15:59:081246 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
[email protected]fb2b8eb2009-04-23 21:03:421247 print "\n--- Changelist " + change_info.name + ":"
[email protected]e3608df2009-11-10 20:22:571248 for filename in change_info.GetFiles():
1249 print "".join(filename)
[email protected]4c22d722010-05-14 19:01:221250 return 0
[email protected]fb2b8eb2009-04-23 21:03:421251
1252
[email protected]62fd6932010-05-27 13:13:231253@no_args
1254def CMDdeleteempties():
[email protected]bfd09ce2009-08-05 21:17:231255 """Delete all changelists that have no files."""
1256 print "\n--- Deleting:"
1257 for cl in GetCLs():
1258 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
[email protected]3a174252010-10-29 15:54:511259 if not len(change_info.GetFiles()):
[email protected]bfd09ce2009-08-05 21:17:231260 print change_info.name
1261 change_info.Delete()
[email protected]4c22d722010-05-14 19:01:221262 return 0
1263
1264
[email protected]62fd6932010-05-27 13:13:231265@no_args
1266def CMDnothave():
[email protected]4c22d722010-05-14 19:01:221267 """Lists files unknown to Subversion."""
[email protected]62fd6932010-05-27 13:13:231268 for filename in UnknownFiles():
[email protected]4c22d722010-05-14 19:01:221269 print "? " + "".join(filename)
1270 return 0
1271
1272
[email protected]62fd6932010-05-27 13:13:231273@attrs(usage='<svn options>')
[email protected]35fe9ad2010-05-25 23:59:541274def CMDdiff(args):
[email protected]62fd6932010-05-27 13:13:231275 """Diffs all files in the changelist or all files that aren't in a CL."""
[email protected]35fe9ad2010-05-25 23:59:541276 files = None
1277 if args:
[email protected]62fd6932010-05-27 13:13:231278 change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
[email protected]35fe9ad2010-05-25 23:59:541279 files = change_info.GetFileNames()
1280 else:
[email protected]707c1482010-06-02 19:52:421281 files = [f[1] for f in GetFilesNotInCL()]
[email protected]38729702010-06-01 23:42:031282
1283 root = GetRepositoryRoot()
1284 cmd = ['svn', 'diff']
1285 cmd.extend([os.path.join(root, x) for x in files])
1286 cmd.extend(args)
1287 return RunShellWithReturnCode(cmd, print_output=True)[1]
[email protected]4c22d722010-05-14 19:01:221288
1289
[email protected]62fd6932010-05-27 13:13:231290@no_args
1291def CMDsettings():
1292 """Prints code review settings for this checkout."""
[email protected]4c22d722010-05-14 19:01:221293 # Force load settings
[email protected]6e29d572010-06-04 17:32:201294 GetCodeReviewSetting("UNKNOWN")
[email protected]4c22d722010-05-14 19:01:221295 del CODEREVIEW_SETTINGS['__just_initialized']
1296 print '\n'.join(("%s: %s" % (str(k), str(v))
1297 for (k,v) in CODEREVIEW_SETTINGS.iteritems()))
1298 return 0
1299
1300
[email protected]35fe9ad2010-05-25 23:59:541301@need_change
1302def CMDdescription(change_info):
[email protected]4c22d722010-05-14 19:01:221303 """Prints the description of the specified change to stdout."""
[email protected]4c22d722010-05-14 19:01:221304 print change_info.description
1305 return 0
1306
1307
[email protected]79b7ef02010-11-01 13:25:131308def CMDdelete(args):
[email protected]4c22d722010-05-14 19:01:221309 """Deletes a changelist."""
[email protected]79b7ef02010-11-01 13:25:131310 if not len(args) == 1:
1311 ErrorExit('You need to pass a change list name')
[email protected]a2d7edf2011-04-04 17:53:451312 filepath = GetChangelistInfoFile(args[0])
1313 if not os.path.isfile(filepath):
1314 ErrorExit('You need to pass a valid change list name')
1315 os.remove(filepath)
[email protected]4c22d722010-05-14 19:01:221316 return 0
1317
1318
[email protected]35fe9ad2010-05-25 23:59:541319def CMDtry(args):
[email protected]62fd6932010-05-27 13:13:231320 """Sends the change to the tryserver to do a test run on your code.
[email protected]4c22d722010-05-14 19:01:221321
1322 To send multiple changes as one path, use a comma-separated list of
1323 changenames. Use 'gcl help try' for more information!"""
1324 # When the change contains no file, send the "changename" positional
1325 # argument to trychange.py.
[email protected]35fe9ad2010-05-25 23:59:541326 # When the command is 'try' and --patchset is used, the patch to try
1327 # is on the Rietveld server.
1328 if not args:
1329 ErrorExit("You need to pass a change list name")
1330 if args[0].find(',') != -1:
1331 change_info = LoadChangelistInfoForMultiple(args[0], GetRepositoryRoot(),
1332 True, True)
1333 else:
1334 change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(),
1335 False, True)
[email protected]4c22d722010-05-14 19:01:221336 if change_info.GetFiles():
[email protected]35fe9ad2010-05-25 23:59:541337 args = args[1:]
[email protected]4c22d722010-05-14 19:01:221338 else:
1339 change_info = None
[email protected]35fe9ad2010-05-25 23:59:541340 return TryChange(change_info, args, swallow_exception=False)
[email protected]4c22d722010-05-14 19:01:221341
1342
[email protected]62fd6932010-05-27 13:13:231343@attrs(usage='<old-name> <new-name>')
[email protected]35fe9ad2010-05-25 23:59:541344def CMDrename(args):
[email protected]4c22d722010-05-14 19:01:221345 """Renames an existing change."""
[email protected]35fe9ad2010-05-25 23:59:541346 if len(args) != 2:
[email protected]4c22d722010-05-14 19:01:221347 ErrorExit("Usage: gcl rename <old-name> <new-name>.")
[email protected]35fe9ad2010-05-25 23:59:541348 src, dst = args
[email protected]4c22d722010-05-14 19:01:221349 src_file = GetChangelistInfoFile(src)
1350 if not os.path.isfile(src_file):
1351 ErrorExit("Change '%s' does not exist." % src)
1352 dst_file = GetChangelistInfoFile(dst)
1353 if os.path.isfile(dst_file):
1354 ErrorExit("Change '%s' already exists; pick a new name." % dst)
1355 os.rename(src_file, dst_file)
1356 print "Change '%s' renamed '%s'." % (src, dst)
1357 return 0
[email protected]bfd09ce2009-08-05 21:17:231358
1359
[email protected]35fe9ad2010-05-25 23:59:541360def CMDpassthru(args):
[email protected]62fd6932010-05-27 13:13:231361 """Everything else that is passed into gcl we redirect to svn.
1362
1363 It assumes a change list name is passed and is converted with the files names.
1364 """
[email protected]35fe9ad2010-05-25 23:59:541365 args = ["svn", args[0]]
1366 if len(args) > 1:
1367 root = GetRepositoryRoot()
1368 change_info = ChangeInfo.Load(args[1], root, True, True)
1369 args.extend([os.path.join(root, x) for x in change_info.GetFileNames()])
1370 return RunShellWithReturnCode(args, print_output=True)[1]
1371
1372
[email protected]62fd6932010-05-27 13:13:231373def Command(name):
1374 return getattr(sys.modules[__name__], 'CMD' + name, None)
1375
1376
1377def GenUsage(command):
1378 """Modify an OptParse object with the function's documentation."""
1379 obj = Command(command)
1380 display = command
1381 more = getattr(obj, 'usage', '')
1382 if command == 'help':
1383 display = '<command>'
[email protected]3a174252010-10-29 15:54:511384 need_change_val = ''
[email protected]62fd6932010-05-27 13:13:231385 if getattr(obj, 'need_change', None):
[email protected]3a174252010-10-29 15:54:511386 need_change_val = ' <change_list>'
[email protected]62fd6932010-05-27 13:13:231387 options = ' [options]'
1388 if getattr(obj, 'no_args', None):
1389 options = ''
[email protected]3a174252010-10-29 15:54:511390 res = 'Usage: gcl %s%s%s %s\n\n' % (display, need_change_val, options, more)
[email protected]62fd6932010-05-27 13:13:231391 res += re.sub('\n ', '\n', obj.__doc__)
1392 return res
1393
1394
1395def CMDhelp(args):
1396 """Prints this help or help for the given command."""
1397 if args and 'CMD' + args[0] in dir(sys.modules[__name__]):
1398 print GenUsage(args[0])
1399
1400 # These commands defer to external tools so give this info too.
1401 if args[0] == 'try':
1402 TryChange(None, ['--help'], swallow_exception=False)
1403 if args[0] == 'upload':
1404 upload.RealMain(['upload.py', '--help'])
1405 return 0
1406
1407 print GenUsage('help')
1408 print sys.modules[__name__].__doc__
1409 print 'version ' + __version__ + '\n'
1410
1411 print('Commands are:\n' + '\n'.join([
1412 ' %-12s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1413 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1414 return 0
1415
1416
[email protected]35fe9ad2010-05-25 23:59:541417def main(argv):
[email protected]c3a15a22010-11-20 03:12:271418 if sys.hexversion < 0x02050000:
1419 print >> sys.stderr, (
1420 '\nYour python version is unsupported, please upgrade.\n')
[email protected]c68f9cb2010-06-17 20:34:181421 if not argv:
1422 argv = ['help']
1423 command = Command(argv[0])
1424 # Help can be run from anywhere.
1425 if command == CMDhelp:
1426 return command(argv[1:])
1427
[email protected]a05be0b2009-06-30 19:13:021428 try:
[email protected]62fd6932010-05-27 13:13:231429 GetRepositoryRoot()
[email protected]31cb48a2011-04-04 18:01:361430 except (gclient_utils.Error, subprocess2.CalledProcessError):
[email protected]58c19382010-09-22 19:53:591431 print >> sys.stderr, 'To use gcl, you need to be in a subversion checkout.'
[email protected]62fd6932010-05-27 13:13:231432 return 1
1433
1434 # Create the directories where we store information about changelists if it
1435 # doesn't exist.
[email protected]807c4462010-07-10 00:45:281436 try:
1437 if not os.path.exists(GetInfoDir()):
1438 os.mkdir(GetInfoDir())
1439 if not os.path.exists(GetChangesDir()):
1440 os.mkdir(GetChangesDir())
1441 if not os.path.exists(GetCacheDir()):
1442 os.mkdir(GetCacheDir())
[email protected]fb2b8eb2009-04-23 21:03:421443
[email protected]807c4462010-07-10 00:45:281444 if command:
1445 return command(argv[1:])
1446 # Unknown command, try to pass that to svn
1447 return CMDpassthru(argv)
[email protected]31cb48a2011-04-04 18:01:361448 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
[email protected]58c19382010-09-22 19:53:591449 print >> sys.stderr, 'Got an exception'
1450 print >> sys.stderr, str(e)
1451 return 1
[email protected]42c7f662010-10-06 23:52:461452 except upload.ClientLoginError, e:
1453 print >> sys.stderr, 'Got an exception logging in to Rietveld'
1454 print >> sys.stderr, str(e)
[email protected]58c19382010-09-22 19:53:591455 except urllib2.HTTPError, e:
1456 if e.code != 500:
1457 raise
1458 print >> sys.stderr, (
1459 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
[email protected]7633e472010-09-22 20:03:141460 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))
[email protected]58c19382010-09-22 19:53:591461 return 1
1462
[email protected]fb2b8eb2009-04-23 21:03:421463
1464if __name__ == "__main__":
[email protected]35625c72011-03-23 17:34:021465 fix_encoding.fix_encoding()
[email protected]35fe9ad2010-05-25 23:59:541466 sys.exit(main(sys.argv[1:]))