blob: 750e4cabb41eed4f872bbc5d0dd9d188ef579b17 [file] [log] [blame]
[email protected]725f1c32011-04-01 20:24:541#!/usr/bin/env python
[email protected]080f37c2012-01-16 14:20:012# Copyright (c) 2012 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]4f6852c2012-04-20 20:39:2011import json
[email protected]8e13a092010-11-02 19:06:0612import optparse
[email protected]fb2b8eb2009-04-23 21:03:4213import os
14import random
15import re
16import string
[email protected]fb2b8eb2009-04-23 21:03:4217import sys
18import tempfile
[email protected]2f6a0d82010-05-12 00:03:3019import time
[email protected]fb2b8eb2009-04-23 21:03:4220import urllib2
21
[email protected]e13e12a2011-09-08 17:10:1122import breakpad # pylint: disable=W0611
23
[email protected]fa3843e2010-11-01 13:34:3724
[email protected]35625c72011-03-23 17:34:0225import fix_encoding
[email protected]5f3eee32009-09-17 00:34:3026import gclient_utils
[email protected]30becc22011-03-17 01:21:2227import presubmit_support
[email protected]cab38e92011-04-09 00:30:5128import rietveld
[email protected]e13e12a2011-09-08 17:10:1129from scm import SVN
[email protected]31cb48a2011-04-04 18:01:3630import subprocess2
[email protected]e13e12a2011-09-08 17:10:1131from third_party import upload
[email protected]c1675e22009-04-27 20:30:4832
[email protected]97335082011-10-20 15:00:1633__version__ = '1.2.1'
[email protected]c1675e22009-04-27 20:30:4834
35
[email protected]fb2b8eb2009-04-23 21:03:4236CODEREVIEW_SETTINGS = {
[email protected]b8260242010-08-19 17:03:1637 # To make gcl send reviews to a server, check in a file named
[email protected]172b6e72010-01-26 00:35:0338 # "codereview.settings" (see |CODEREVIEW_SETTINGS_FILE| below) to your
39 # project's base directory and add the following line to codereview.settings:
40 # CODE_REVIEW_SERVER: codereview.yourserver.org
[email protected]fb2b8eb2009-04-23 21:03:4241}
42
[email protected]fb2b8eb2009-04-23 21:03:4243# globals that store the root of the current repository and the directory where
44# we store information about changelists.
[email protected]98fc2b92009-05-21 14:11:5145REPOSITORY_ROOT = ""
[email protected]fb2b8eb2009-04-23 21:03:4246
47# Filename where we store repository specific information for gcl.
48CODEREVIEW_SETTINGS_FILE = "codereview.settings"
[email protected]b8260242010-08-19 17:03:1649CODEREVIEW_SETTINGS_FILE_NOT_FOUND = (
50 'No %s file found. Please add one.' % CODEREVIEW_SETTINGS_FILE)
[email protected]fb2b8eb2009-04-23 21:03:4251
52# Warning message when the change appears to be missing tests.
53MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!"
54
[email protected]98fc2b92009-05-21 14:11:5155# Global cache of files cached in GetCacheDir().
56FILES_CACHE = {}
[email protected]fb2b8eb2009-04-23 21:03:4257
[email protected]4c22d722010-05-14 19:01:2258# Valid extensions for files we want to lint.
59DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
60DEFAULT_LINT_IGNORE_REGEX = r"$^"
61
[email protected]30becc22011-03-17 01:21:2262REVIEWERS_REGEX = r'\s*R=(.+)'
[email protected]4c22d722010-05-14 19:01:2263
[email protected]e5299012010-04-07 18:02:2664def CheckHomeForFile(filename):
65 """Checks the users home dir for the existence of the given file. Returns
66 the path to the file if it's there, or None if it is not.
67 """
68 home_vars = ['HOME']
69 if sys.platform in ('cygwin', 'win32'):
70 home_vars.append('USERPROFILE')
71 for home_var in home_vars:
72 home = os.getenv(home_var)
73 if home != None:
74 full_path = os.path.join(home, filename)
75 if os.path.exists(full_path):
76 return full_path
77 return None
[email protected]fb2b8eb2009-04-23 21:03:4278
[email protected]35fe9ad2010-05-25 23:59:5479
[email protected]62fd6932010-05-27 13:13:2380def UnknownFiles():
81 """Runs svn status and returns unknown files."""
[email protected]d579fcf2011-12-13 20:36:0382 return [
83 item[1] for item in SVN.CaptureStatus([], GetRepositoryRoot())
84 if item[0][0] == '?'
85 ]
[email protected]207fdf32009-04-28 19:57:0186
87
[email protected]fb2b8eb2009-04-23 21:03:4288def GetRepositoryRoot():
89 """Returns the top level directory of the current repository.
90
91 The directory is returned as an absolute path.
92 """
[email protected]98fc2b92009-05-21 14:11:5193 global REPOSITORY_ROOT
94 if not REPOSITORY_ROOT:
[email protected]94b1ee92009-12-19 20:27:2095 REPOSITORY_ROOT = SVN.GetCheckoutRoot(os.getcwd())
96 if not REPOSITORY_ROOT:
[email protected]5f3eee32009-09-17 00:34:3097 raise gclient_utils.Error("gcl run outside of repository")
[email protected]98fc2b92009-05-21 14:11:5198 return REPOSITORY_ROOT
[email protected]fb2b8eb2009-04-23 21:03:4299
100
101def GetInfoDir():
102 """Returns the directory where gcl info files are stored."""
[email protected]374d65e2009-05-21 14:00:52103 return os.path.join(GetRepositoryRoot(), '.svn', 'gcl_info')
104
105
106def GetChangesDir():
107 """Returns the directory where gcl change files are stored."""
108 return os.path.join(GetInfoDir(), 'changes')
[email protected]fb2b8eb2009-04-23 21:03:42109
110
[email protected]98fc2b92009-05-21 14:11:51111def GetCacheDir():
112 """Returns the directory where gcl change files are stored."""
113 return os.path.join(GetInfoDir(), 'cache')
114
115
116def GetCachedFile(filename, max_age=60*60*24*3, use_root=False):
117 """Retrieves a file from the repository and caches it in GetCacheDir() for
118 max_age seconds.
119
120 use_root: If False, look up the arborescence for the first match, otherwise go
121 directory to the root repository.
122
123 Note: The cache will be inconsistent if the same file is retrieved with both
[email protected]bb816382009-10-29 01:38:02124 use_root=True and use_root=False. Don't be stupid.
[email protected]98fc2b92009-05-21 14:11:51125 """
[email protected]98fc2b92009-05-21 14:11:51126 if filename not in FILES_CACHE:
127 # Don't try to look up twice.
128 FILES_CACHE[filename] = None
[email protected]9b613272009-04-24 01:28:28129 # First we check if we have a cached version.
[email protected]a05be0b2009-06-30 19:13:02130 try:
131 cached_file = os.path.join(GetCacheDir(), filename)
[email protected]31cb48a2011-04-04 18:01:36132 except (gclient_utils.Error, subprocess2.CalledProcessError):
[email protected]a05be0b2009-06-30 19:13:02133 return None
[email protected]98fc2b92009-05-21 14:11:51134 if (not os.path.exists(cached_file) or
[email protected]2f6a0d82010-05-12 00:03:30135 (time.time() - os.stat(cached_file).st_mtime) > max_age):
[email protected]d579fcf2011-12-13 20:36:03136 dir_info = SVN.CaptureLocalInfo([], '.')
[email protected]b3b494f2010-08-31 20:40:08137 repo_root = dir_info['Repository Root']
[email protected]98fc2b92009-05-21 14:11:51138 if use_root:
139 url_path = repo_root
140 else:
[email protected]b3b494f2010-08-31 20:40:08141 url_path = dir_info['URL']
[email protected]9b613272009-04-24 01:28:28142 while True:
[email protected]fa44e4a2009-12-03 01:41:13143 # Look in the repository at the current level for the file.
[email protected]b6ee1672010-08-19 17:06:07144 for _ in range(5):
[email protected]b3b494f2010-08-31 20:40:08145 content = None
[email protected]b6ee1672010-08-19 17:06:07146 try:
147 # Take advantage of the fact that svn won't output to stderr in case
148 # of success but will do in case of failure so don't mind putting
149 # stderr into content_array.
150 content_array = []
[email protected]b3b494f2010-08-31 20:40:08151 svn_path = url_path + '/' + filename
[email protected]17368002010-09-01 23:36:32152 args = ['svn', 'cat', svn_path]
[email protected]02913a52010-08-25 20:50:50153 if sys.platform != 'darwin':
154 # MacOSX 10.5.2 has a bug with svn 1.4.4 that will trigger the
155 # 'Can\'t get username or password' and can be fixed easily.
156 # The fix doesn't work if the user upgraded to svn 1.6.x. Bleh.
157 # I don't have time to fix their broken stuff.
158 args.append('--non-interactive')
[email protected]17d01792010-09-01 18:07:10159 gclient_utils.CheckCallAndFilter(
160 args, cwd='.', filter_fn=content_array.append)
[email protected]b6ee1672010-08-19 17:06:07161 # Exit the loop if the file was found. Override content.
162 content = '\n'.join(content_array)
163 break
[email protected]31cb48a2011-04-04 18:01:36164 except (gclient_utils.Error, subprocess2.CalledProcessError):
[email protected]b6ee1672010-08-19 17:06:07165 if content_array[0].startswith(
166 'svn: Can\'t get username or password'):
167 ErrorExit('Your svn credentials expired. Please run svn update '
168 'to fix the cached credentials')
[email protected]3c842982010-08-20 17:26:49169 if content_array[0].startswith('svn: Can\'t get password'):
170 ErrorExit('If are using a Mac and svn --version shows 1.4.x, '
171 'please hack gcl.py to remove --non-interactive usage, it\'s'
172 'a bug on your installed copy')
[email protected]dcd15222010-12-21 22:43:19173 if (content_array[0].startswith('svn: File not found:') or
174 content_array[0].endswith('path not found')):
175 break
176 # Otherwise, fall through to trying again.
[email protected]b6ee1672010-08-19 17:06:07177 if content:
[email protected]9b613272009-04-24 01:28:28178 break
[email protected]9b613272009-04-24 01:28:28179 if url_path == repo_root:
180 # Reached the root. Abandoning search.
[email protected]98fc2b92009-05-21 14:11:51181 break
[email protected]9b613272009-04-24 01:28:28182 # Go up one level to try again.
183 url_path = os.path.dirname(url_path)
[email protected]b3b494f2010-08-31 20:40:08184 if content is not None or filename != CODEREVIEW_SETTINGS_FILE:
185 # Write a cached version even if there isn't a file, so we don't try to
186 # fetch it each time. codereview.settings must always be present so do
187 # not cache negative.
188 gclient_utils.FileWrite(cached_file, content or '')
[email protected]98fc2b92009-05-21 14:11:51189 else:
[email protected]0fca4f32009-12-18 15:14:34190 content = gclient_utils.FileRead(cached_file, 'r')
[email protected]98fc2b92009-05-21 14:11:51191 # Keep the content cached in memory.
192 FILES_CACHE[filename] = content
193 return FILES_CACHE[filename]
[email protected]9b613272009-04-24 01:28:28194
[email protected]98fc2b92009-05-21 14:11:51195
196def GetCodeReviewSetting(key):
197 """Returns a value for the given key for this repository."""
198 # Use '__just_initialized' as a flag to determine if the settings were
199 # already initialized.
[email protected]98fc2b92009-05-21 14:11:51200 if '__just_initialized' not in CODEREVIEW_SETTINGS:
[email protected]b0442182009-06-05 14:20:47201 settings_file = GetCachedFile(CODEREVIEW_SETTINGS_FILE)
202 if settings_file:
[email protected]99ac1c52012-01-16 14:52:12203 CODEREVIEW_SETTINGS.update(
204 gclient_utils.ParseCodereviewSettingsContent(settings_file))
[email protected]98fc2b92009-05-21 14:11:51205 CODEREVIEW_SETTINGS.setdefault('__just_initialized', None)
[email protected]fb2b8eb2009-04-23 21:03:42206 return CODEREVIEW_SETTINGS.get(key, "")
207
208
[email protected]fb2b8eb2009-04-23 21:03:42209def Warn(msg):
[email protected]6e29d572010-06-04 17:32:20210 print >> sys.stderr, msg
[email protected]223b7192010-06-04 18:52:58211
212
213def ErrorExit(msg):
214 print >> sys.stderr, msg
215 sys.exit(1)
[email protected]fb2b8eb2009-04-23 21:03:42216
217
[email protected]97335082011-10-20 15:00:16218def RunShellWithReturnCode(command, print_output=False):
219 """Executes a command and returns the output and the return code."""
220 p = subprocess2.Popen(
[email protected]d79d4192011-12-19 22:25:44221 command,
222 cwd=GetRepositoryRoot(),
223 stdout=subprocess2.PIPE,
224 stderr=subprocess2.STDOUT,
225 universal_newlines=True)
[email protected]97335082011-10-20 15:00:16226 if print_output:
227 output_array = []
228 while True:
229 line = p.stdout.readline()
230 if not line:
231 break
232 if print_output:
233 print line.strip('\n')
234 output_array.append(line)
235 output = "".join(output_array)
236 else:
237 output = p.stdout.read()
238 p.wait()
239 p.stdout.close()
240 return output, p.returncode
241
242
243def RunShell(command, print_output=False):
244 """Executes a command and returns the output."""
245 return RunShellWithReturnCode(command, print_output)[0]
246
247
[email protected]51ee0072009-06-08 19:20:05248def FilterFlag(args, flag):
249 """Returns True if the flag is present in args list.
250
251 The flag is removed from args if present.
252 """
253 if flag in args:
254 args.remove(flag)
255 return True
256 return False
257
258
[email protected]be0d1ca2009-05-12 19:23:02259class ChangeInfo(object):
[email protected]fb2b8eb2009-04-23 21:03:42260 """Holds information about a changelist.
261
[email protected]32ba2602009-06-06 18:44:48262 name: change name.
263 issue: the Rietveld issue number or 0 if it hasn't been uploaded yet.
264 patchset: the Rietveld latest patchset number or 0.
[email protected]fb2b8eb2009-04-23 21:03:42265 description: the description.
266 files: a list of 2 tuple containing (status, filename) of changed files,
267 with paths being relative to the top repository directory.
[email protected]8d5c9a52009-06-12 15:59:08268 local_root: Local root directory
[email protected]bf1fdca2010-11-01 18:05:36269 rietveld: rietveld server for this change
[email protected]fb2b8eb2009-04-23 21:03:42270 """
[email protected]bf1fdca2010-11-01 18:05:36271 # Kept for unit test support. This is for the old format, it's deprecated.
[email protected]428342a2011-11-10 15:46:33272 SEPARATOR = "\n-----\n"
[email protected]32ba2602009-06-06 18:44:48273
[email protected]ea452b32009-11-22 20:04:31274 def __init__(self, name, issue, patchset, description, files, local_root,
[email protected]6493ed12011-04-14 13:51:00275 rietveld_url, needs_upload):
[email protected]fb2b8eb2009-04-23 21:03:42276 self.name = name
[email protected]32ba2602009-06-06 18:44:48277 self.issue = int(issue)
278 self.patchset = int(patchset)
[email protected]20254fc2011-03-22 18:28:59279 self._description = None
[email protected]20254fc2011-03-22 18:28:59280 self._reviewers = None
[email protected]30becc22011-03-17 01:21:22281 self._set_description(description)
[email protected]be0d1ca2009-05-12 19:23:02282 if files is None:
283 files = []
[email protected]17f59f22009-06-12 13:27:24284 self._files = files
[email protected]fb2b8eb2009-04-23 21:03:42285 self.patch = None
[email protected]8d5c9a52009-06-12 15:59:08286 self._local_root = local_root
[email protected]ea452b32009-11-22 20:04:31287 self.needs_upload = needs_upload
[email protected]eb5edbc2012-01-16 17:03:28288 self.rietveld = gclient_utils.UpgradeToHttps(
289 rietveld_url or GetCodeReviewSetting('CODE_REVIEW_SERVER'))
[email protected]cab38e92011-04-09 00:30:51290 self._rpc_server = None
[email protected]ea452b32009-11-22 20:04:31291
[email protected]30becc22011-03-17 01:21:22292 def _get_description(self):
[email protected]20254fc2011-03-22 18:28:59293 return self._description
[email protected]30becc22011-03-17 01:21:22294
295 def _set_description(self, description):
[email protected]20254fc2011-03-22 18:28:59296 # TODO(dpranke): Cloned from git_cl.py. These should be shared.
297 if not description:
298 self._description = description
299 return
300
301 parsed_lines = []
302 reviewers_re = re.compile(REVIEWERS_REGEX)
303 reviewers = ''
[email protected]20254fc2011-03-22 18:28:59304 for l in description.splitlines():
[email protected]20254fc2011-03-22 18:28:59305 matched_reviewers = reviewers_re.match(l)
306 if matched_reviewers:
307 reviewers = matched_reviewers.group(1).split(',')
308 parsed_lines.append(l)
[email protected]20254fc2011-03-22 18:28:59309 self._reviewers = reviewers
310 self._description = '\n'.join(parsed_lines)
[email protected]30becc22011-03-17 01:21:22311
312 description = property(_get_description, _set_description)
313
314 @property
315 def reviewers(self):
[email protected]20254fc2011-03-22 18:28:59316 return self._reviewers
[email protected]30becc22011-03-17 01:21:22317
[email protected]ea452b32009-11-22 20:04:31318 def NeedsUpload(self):
319 return self.needs_upload
[email protected]fb2b8eb2009-04-23 21:03:42320
[email protected]17f59f22009-06-12 13:27:24321 def GetFileNames(self):
322 """Returns the list of file names included in this change."""
[email protected]e3608df2009-11-10 20:22:57323 return [f[1] for f in self._files]
[email protected]17f59f22009-06-12 13:27:24324
325 def GetFiles(self):
326 """Returns the list of files included in this change with their status."""
327 return self._files
328
329 def GetLocalRoot(self):
330 """Returns the local repository checkout root directory."""
331 return self._local_root
[email protected]fb2b8eb2009-04-23 21:03:42332
[email protected]f0dfba32009-08-07 22:03:37333 def Exists(self):
334 """Returns True if this change already exists (i.e., is not new)."""
335 return (self.issue or self.description or self._files)
336
[email protected]fb2b8eb2009-04-23 21:03:42337 def _NonDeletedFileList(self):
338 """Returns a list of files in this change, not including deleted files."""
[email protected]e3608df2009-11-10 20:22:57339 return [f[1] for f in self.GetFiles()
340 if not f[0].startswith("D")]
[email protected]fb2b8eb2009-04-23 21:03:42341
342 def _AddedFileList(self):
343 """Returns a list of files added in this change."""
[email protected]e3608df2009-11-10 20:22:57344 return [f[1] for f in self.GetFiles() if f[0].startswith("A")]
[email protected]fb2b8eb2009-04-23 21:03:42345
346 def Save(self):
347 """Writes the changelist information to disk."""
[email protected]fa3843e2010-11-01 13:34:37348 data = json.dumps({
349 'issue': self.issue,
350 'patchset': self.patchset,
351 'needs_upload': self.NeedsUpload(),
352 'files': self.GetFiles(),
[email protected]20254fc2011-03-22 18:28:59353 'description': self.description,
[email protected]bf1fdca2010-11-01 18:05:36354 'rietveld': self.rietveld,
[email protected]fa3843e2010-11-01 13:34:37355 }, sort_keys=True, indent=2)
[email protected]fc83c112009-12-18 15:14:10356 gclient_utils.FileWrite(GetChangelistInfoFile(self.name), data)
[email protected]fb2b8eb2009-04-23 21:03:42357
358 def Delete(self):
359 """Removes the changelist information from disk."""
360 os.remove(GetChangelistInfoFile(self.name))
361
[email protected]cab38e92011-04-09 00:30:51362 def RpcServer(self):
363 if not self._rpc_server:
364 if not self.rietveld:
365 ErrorExit(CODEREVIEW_SETTINGS_FILE_NOT_FOUND)
366 self._rpc_server = rietveld.Rietveld(self.rietveld, None, None)
367 return self._rpc_server
368
[email protected]fb2b8eb2009-04-23 21:03:42369 def CloseIssue(self):
370 """Closes the Rietveld issue for this changelist."""
[email protected]41db6ef2010-11-17 19:42:34371 # Newer versions of Rietveld require us to pass an XSRF token to POST, so
372 # we fetch it from the server.
373 xsrf_token = self.SendToRietveld(
374 '/xsrf_token',
375 extra_headers={'X-Requesting-XSRF-Token': '1'})
376
377 # You cannot close an issue with a GET.
378 # We pass an empty string for the data so it is a POST rather than a GET.
379 data = [("description", self.description),
380 ("xsrf_token", xsrf_token)]
[email protected]fb2b8eb2009-04-23 21:03:42381 ctype, body = upload.EncodeMultipartFormData(data, [])
[email protected]41db6ef2010-11-17 19:42:34382 self.SendToRietveld('/%d/close' % self.issue, payload=body,
383 content_type=ctype)
[email protected]fb2b8eb2009-04-23 21:03:42384
385 def UpdateRietveldDescription(self):
386 """Sets the description for an issue on Rietveld."""
387 data = [("description", self.description),]
388 ctype, body = upload.EncodeMultipartFormData(data, [])
[email protected]41db6ef2010-11-17 19:42:34389 self.SendToRietveld('/%d/description' % self.issue, payload=body,
390 content_type=ctype)
[email protected]bf1fdca2010-11-01 18:05:36391
392 def GetIssueDescription(self):
393 """Returns the issue description from Rietveld."""
394 return self.SendToRietveld('/%d/description' % self.issue)
395
396 def PrimeLint(self):
397 """Do background work on Rietveld to lint the file so that the results are
398 ready when the issue is viewed."""
399 if self.issue and self.patchset:
400 self.SendToRietveld('/lint/issue%s_%s' % (self.issue, self.patchset),
401 timeout=1)
402
[email protected]41db6ef2010-11-17 19:42:34403 def SendToRietveld(self, request_path, timeout=None, **kwargs):
[email protected]bf1fdca2010-11-01 18:05:36404 """Send a POST/GET to Rietveld. Returns the response body."""
[email protected]bf1fdca2010-11-01 18:05:36405 try:
[email protected]cab38e92011-04-09 00:30:51406 return self.RpcServer().Send(request_path, timeout=timeout, **kwargs)
[email protected]bf1fdca2010-11-01 18:05:36407 except urllib2.URLError:
408 if timeout is None:
409 ErrorExit('Error accessing url %s' % request_path)
410 else:
411 return None
[email protected]fb2b8eb2009-04-23 21:03:42412
413 def MissingTests(self):
414 """Returns True if the change looks like it needs unit tests but has none.
415
416 A change needs unit tests if it contains any new source files or methods.
417 """
418 SOURCE_SUFFIXES = [".cc", ".cpp", ".c", ".m", ".mm"]
419 # Ignore third_party entirely.
[email protected]e3608df2009-11-10 20:22:57420 files = [f for f in self._NonDeletedFileList()
421 if f.find("third_party") == -1]
422 added_files = [f for f in self._AddedFileList()
423 if f.find("third_party") == -1]
[email protected]fb2b8eb2009-04-23 21:03:42424
425 # If the change is entirely in third_party, we're done.
426 if len(files) == 0:
427 return False
428
429 # Any new or modified test files?
430 # A test file's name ends with "test.*" or "tests.*".
431 test_files = [test for test in files
432 if os.path.splitext(test)[0].rstrip("s").endswith("test")]
433 if len(test_files) > 0:
434 return False
435
436 # Any new source files?
[email protected]e3608df2009-11-10 20:22:57437 source_files = [item for item in added_files
438 if os.path.splitext(item)[1] in SOURCE_SUFFIXES]
[email protected]fb2b8eb2009-04-23 21:03:42439 if len(source_files) > 0:
440 return True
441
442 # Do the long test, checking the files for new methods.
443 return self._HasNewMethod()
444
445 def _HasNewMethod(self):
446 """Returns True if the changeset contains any new functions, or if a
447 function signature has been changed.
448
449 A function is identified by starting flush left, containing a "(" before
450 the next flush-left line, and either ending with "{" before the next
451 flush-left line or being followed by an unindented "{".
452
453 Currently this returns True for new methods, new static functions, and
454 methods or functions whose signatures have been changed.
455
456 Inline methods added to header files won't be detected by this. That's
457 acceptable for purposes of determining if a unit test is needed, since
458 inline methods should be trivial.
459 """
460 # To check for methods added to source or header files, we need the diffs.
461 # We'll generate them all, since there aren't likely to be many files
462 # apart from source and headers; besides, we'll want them all if we're
463 # uploading anyway.
464 if self.patch is None:
[email protected]17f59f22009-06-12 13:27:24465 self.patch = GenerateDiff(self.GetFileNames())
[email protected]fb2b8eb2009-04-23 21:03:42466
467 definition = ""
468 for line in self.patch.splitlines():
469 if not line.startswith("+"):
470 continue
471 line = line.strip("+").rstrip(" \t")
472 # Skip empty lines, comments, and preprocessor directives.
473 # TODO(pamg): Handle multiline comments if it turns out to be a problem.
474 if line == "" or line.startswith("/") or line.startswith("#"):
475 continue
476
477 # A possible definition ending with "{" is complete, so check it.
478 if definition.endswith("{"):
479 if definition.find("(") != -1:
480 return True
481 definition = ""
482
483 # A { or an indented line, when we're in a definition, continues it.
484 if (definition != "" and
485 (line == "{" or line.startswith(" ") or line.startswith("\t"))):
486 definition += line
487
488 # A flush-left line starts a new possible function definition.
489 elif not line.startswith(" ") and not line.startswith("\t"):
490 definition = line
491
492 return False
493
[email protected]32ba2602009-06-06 18:44:48494 @staticmethod
[email protected]8d5c9a52009-06-12 15:59:08495 def Load(changename, local_root, fail_on_not_found, update_status):
[email protected]32ba2602009-06-06 18:44:48496 """Gets information about a changelist.
[email protected]fb2b8eb2009-04-23 21:03:42497
[email protected]32ba2602009-06-06 18:44:48498 Args:
499 fail_on_not_found: if True, this function will quit the program if the
500 changelist doesn't exist.
501 update_status: if True, the svn status will be updated for all the files
502 and unchanged files will be removed.
503
504 Returns: a ChangeInfo object.
505 """
506 info_file = GetChangelistInfoFile(changename)
507 if not os.path.exists(info_file):
508 if fail_on_not_found:
509 ErrorExit("Changelist " + changename + " not found.")
[email protected]6493ed12011-04-14 13:51:00510 return ChangeInfo(changename, 0, 0, '', None, local_root, None, False)
[email protected]7af6c4d2011-09-26 21:04:18511 content = gclient_utils.FileRead(info_file)
[email protected]8a8ea022010-11-01 13:27:32512 save = False
513 try:
[email protected]fa3843e2010-11-01 13:34:37514 values = ChangeInfo._LoadNewFormat(content)
[email protected]8a8ea022010-11-01 13:27:32515 except ValueError:
[email protected]fa3843e2010-11-01 13:34:37516 try:
517 values = ChangeInfo._LoadOldFormat(content)
518 save = True
519 except ValueError:
520 ErrorExit(
521 ('Changelist file %s is corrupt.\n'
522 'Either run "gcl delete %s" or manually edit the file') % (
523 info_file, changename))
[email protected]8a8ea022010-11-01 13:27:32524 files = values['files']
[email protected]32ba2602009-06-06 18:44:48525 if update_status:
[email protected]79b7ef02010-11-01 13:25:13526 for item in files[:]:
[email protected]d579fcf2011-12-13 20:36:03527 status_result = SVN.CaptureStatus(item[1], local_root)
[email protected]32ba2602009-06-06 18:44:48528 if not status_result or not status_result[0][0]:
529 # File has been reverted.
530 save = True
[email protected]e3608df2009-11-10 20:22:57531 files.remove(item)
[email protected]32ba2602009-06-06 18:44:48532 continue
533 status = status_result[0][0]
[email protected]e3608df2009-11-10 20:22:57534 if status != item[0]:
[email protected]32ba2602009-06-06 18:44:48535 save = True
[email protected]e3608df2009-11-10 20:22:57536 files[files.index(item)] = (status, item[1])
[email protected]6493ed12011-04-14 13:51:00537 change_info = ChangeInfo(
538 changename,
539 values['issue'],
540 values['patchset'],
541 values['description'],
542 files,
543 local_root,
544 values.get('rietveld'),
545 values['needs_upload'])
[email protected]32ba2602009-06-06 18:44:48546 if save:
547 change_info.Save()
548 return change_info
[email protected]fb2b8eb2009-04-23 21:03:42549
[email protected]8a8ea022010-11-01 13:27:32550 @staticmethod
551 def _LoadOldFormat(content):
[email protected]bf1fdca2010-11-01 18:05:36552 # The info files have the following format:
553 # issue_id, patchset\n (, patchset is optional)
[email protected]428342a2011-11-10 15:46:33554 # SEPARATOR\n
[email protected]bf1fdca2010-11-01 18:05:36555 # filepath1\n
556 # filepath2\n
557 # .
558 # .
559 # filepathn\n
[email protected]428342a2011-11-10 15:46:33560 # SEPARATOR\n
[email protected]bf1fdca2010-11-01 18:05:36561 # description
[email protected]428342a2011-11-10 15:46:33562 split_data = content.split(ChangeInfo.SEPARATOR, 2)
[email protected]8a8ea022010-11-01 13:27:32563 if len(split_data) != 3:
564 raise ValueError('Bad change format')
565 values = {
566 'issue': 0,
567 'patchset': 0,
568 'needs_upload': False,
569 'files': [],
570 }
571 items = split_data[0].split(', ')
572 if items[0]:
573 values['issue'] = int(items[0])
574 if len(items) > 1:
575 values['patchset'] = int(items[1])
576 if len(items) > 2:
577 values['needs_upload'] = (items[2] == "dirty")
578 for line in split_data[1].splitlines():
579 status = line[:7]
580 filename = line[7:]
581 values['files'].append((status, filename))
582 values['description'] = split_data[2]
583 return values
584
[email protected]fa3843e2010-11-01 13:34:37585 @staticmethod
586 def _LoadNewFormat(content):
587 return json.loads(content)
588
[email protected]d79d4192011-12-19 22:25:44589 def __str__(self):
590 out = ['%s:' % self.__class__.__name__]
591 for k in dir(self):
592 if k.startswith('__'):
593 continue
594 v = getattr(self, k)
595 if v is self or callable(getattr(self, k)):
596 continue
597 out.append(' %s: %r' % (k, v))
598 return '\n'.join(out)
599
[email protected]fb2b8eb2009-04-23 21:03:42600
601def GetChangelistInfoFile(changename):
602 """Returns the file that stores information about a changelist."""
603 if not changename or re.search(r'[^\w-]', changename):
604 ErrorExit("Invalid changelist name: " + changename)
[email protected]374d65e2009-05-21 14:00:52605 return os.path.join(GetChangesDir(), changename)
[email protected]fb2b8eb2009-04-23 21:03:42606
607
[email protected]8d5c9a52009-06-12 15:59:08608def LoadChangelistInfoForMultiple(changenames, local_root, fail_on_not_found,
609 update_status):
[email protected]fb2b8eb2009-04-23 21:03:42610 """Loads many changes and merge their files list into one pseudo change.
611
612 This is mainly usefull to concatenate many changes into one for a 'gcl try'.
613 """
614 changes = changenames.split(',')
[email protected]6493ed12011-04-14 13:51:00615 aggregate_change_info = ChangeInfo(
616 changenames, 0, 0, '', None, local_root, None, False)
[email protected]fb2b8eb2009-04-23 21:03:42617 for change in changes:
[email protected]6493ed12011-04-14 13:51:00618 aggregate_change_info._files += ChangeInfo.Load(
619 change, local_root, fail_on_not_found, update_status).GetFiles()
[email protected]fb2b8eb2009-04-23 21:03:42620 return aggregate_change_info
621
622
[email protected]fb2b8eb2009-04-23 21:03:42623def GetCLs():
624 """Returns a list of all the changelists in this repository."""
[email protected]374d65e2009-05-21 14:00:52625 cls = os.listdir(GetChangesDir())
[email protected]fb2b8eb2009-04-23 21:03:42626 if CODEREVIEW_SETTINGS_FILE in cls:
627 cls.remove(CODEREVIEW_SETTINGS_FILE)
628 return cls
629
630
631def GenerateChangeName():
632 """Generate a random changelist name."""
633 random.seed()
634 current_cl_names = GetCLs()
635 while True:
636 cl_name = (random.choice(string.ascii_lowercase) +
637 random.choice(string.digits) +
638 random.choice(string.ascii_lowercase) +
639 random.choice(string.digits))
640 if cl_name not in current_cl_names:
641 return cl_name
642
643
644def GetModifiedFiles():
645 """Returns a set that maps from changelist name to (status,filename) tuples.
646
647 Files not in a changelist have an empty changelist name. Filenames are in
648 relation to the top level directory of the current repository. Note that
649 only the current directory and subdirectories are scanned, in order to
650 improve performance while still being flexible.
651 """
652 files = {}
653
654 # Since the files are normalized to the root folder of the repositary, figure
655 # out what we need to add to the paths.
656 dir_prefix = os.getcwd()[len(GetRepositoryRoot()):].strip(os.sep)
657
658 # Get a list of all files in changelists.
659 files_in_cl = {}
660 for cl in GetCLs():
[email protected]8d5c9a52009-06-12 15:59:08661 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(),
662 fail_on_not_found=True, update_status=False)
[email protected]17f59f22009-06-12 13:27:24663 for status, filename in change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42664 files_in_cl[filename] = change_info.name
665
[email protected]d79d4192011-12-19 22:25:44666 # Get all the modified files down the current directory.
667 for line in SVN.CaptureStatus(None, os.getcwd()):
[email protected]207fdf32009-04-28 19:57:01668 status = line[0]
669 filename = line[1]
670 if status[0] == "?":
[email protected]fb2b8eb2009-04-23 21:03:42671 continue
[email protected]fb2b8eb2009-04-23 21:03:42672 if dir_prefix:
673 filename = os.path.join(dir_prefix, filename)
674 change_list_name = ""
675 if filename in files_in_cl:
676 change_list_name = files_in_cl[filename]
677 files.setdefault(change_list_name, []).append((status, filename))
678
679 return files
680
681
682def GetFilesNotInCL():
683 """Returns a list of tuples (status,filename) that aren't in any changelists.
684
685 See docstring of GetModifiedFiles for information about path of files and
686 which directories are scanned.
687 """
688 modified_files = GetModifiedFiles()
689 if "" not in modified_files:
690 return []
691 return modified_files[""]
692
693
[email protected]4c22d722010-05-14 19:01:22694def ListFiles(show_unknown_files):
[email protected]fb2b8eb2009-04-23 21:03:42695 files = GetModifiedFiles()
696 cl_keys = files.keys()
697 cl_keys.sort()
698 for cl_name in cl_keys:
[email protected]88c32d82009-10-12 18:24:05699 if not cl_name:
700 continue
701 note = ""
702 change_info = ChangeInfo.Load(cl_name, GetRepositoryRoot(),
703 fail_on_not_found=True, update_status=False)
704 if len(change_info.GetFiles()) != len(files[cl_name]):
705 note = " (Note: this changelist contains files outside this directory)"
706 print "\n--- Changelist " + cl_name + note + ":"
[email protected]e3608df2009-11-10 20:22:57707 for filename in files[cl_name]:
708 print "".join(filename)
[email protected]88c32d82009-10-12 18:24:05709 if show_unknown_files:
[email protected]62fd6932010-05-27 13:13:23710 unknown_files = UnknownFiles()
[email protected]88c32d82009-10-12 18:24:05711 if (files.get('') or (show_unknown_files and len(unknown_files))):
712 print "\n--- Not in any changelist:"
[email protected]e3608df2009-11-10 20:22:57713 for item in files.get('', []):
714 print "".join(item)
[email protected]88c32d82009-10-12 18:24:05715 if show_unknown_files:
[email protected]e3608df2009-11-10 20:22:57716 for filename in unknown_files:
717 print "? %s" % filename
[email protected]4c22d722010-05-14 19:01:22718 return 0
[email protected]fb2b8eb2009-04-23 21:03:42719
720
[email protected]d579fcf2011-12-13 20:36:03721def GenerateDiff(files):
722 return SVN.GenerateDiff(
723 files, GetRepositoryRoot(), full_move=False, revision=None)
[email protected]fb2b8eb2009-04-23 21:03:42724
[email protected]51ee0072009-06-08 19:20:05725
726def OptionallyDoPresubmitChecks(change_info, committing, args):
727 if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"):
[email protected]080f37c2012-01-16 14:20:01728 breakpad.SendStack(
[email protected]2e72bb12012-01-17 15:18:35729 breakpad.DEFAULT_URL + '/breakpad',
[email protected]080f37c2012-01-16 14:20:01730 'GclHooksBypassedCommit',
731 'Issue %s/%s bypassed hook when committing' %
[email protected]2e72bb12012-01-17 15:18:35732 (change_info.rietveld, change_info.issue),
733 verbose=False)
[email protected]30becc22011-03-17 01:21:22734 return presubmit_support.PresubmitOutput()
[email protected]b0dfd352009-06-10 14:12:54735 return DoPresubmitChecks(change_info, committing, True)
[email protected]51ee0072009-06-08 19:20:05736
737
[email protected]62fd6932010-05-27 13:13:23738def defer_attributes(a, b):
739 """Copy attributes from an object (like a function) to another."""
740 for x in dir(a):
741 if not getattr(b, x, None):
742 setattr(b, x, getattr(a, x))
743
744
[email protected]35fe9ad2010-05-25 23:59:54745def need_change(function):
746 """Converts args -> change_info."""
[email protected]3a174252010-10-29 15:54:51747 # pylint: disable=W0612,W0621
[email protected]35fe9ad2010-05-25 23:59:54748 def hook(args):
749 if not len(args) == 1:
750 ErrorExit("You need to pass a change list name")
751 change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(), True, True)
752 return function(change_info)
[email protected]62fd6932010-05-27 13:13:23753 defer_attributes(function, hook)
754 hook.need_change = True
755 hook.no_args = True
[email protected]35fe9ad2010-05-25 23:59:54756 return hook
757
758
[email protected]62fd6932010-05-27 13:13:23759def need_change_and_args(function):
760 """Converts args -> change_info."""
[email protected]3a174252010-10-29 15:54:51761 # pylint: disable=W0612,W0621
[email protected]62fd6932010-05-27 13:13:23762 def hook(args):
[email protected]e56fe822010-05-28 20:36:57763 if not args:
764 ErrorExit("You need to pass a change list name")
[email protected]62fd6932010-05-27 13:13:23765 change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
766 return function(change_info, args)
767 defer_attributes(function, hook)
768 hook.need_change = True
769 return hook
770
771
772def no_args(function):
773 """Make sure no args are passed."""
[email protected]3a174252010-10-29 15:54:51774 # pylint: disable=W0612,W0621
[email protected]62fd6932010-05-27 13:13:23775 def hook(args):
776 if args:
777 ErrorExit("Doesn't support arguments")
778 return function()
779 defer_attributes(function, hook)
780 hook.no_args = True
781 return hook
782
783
784def attrs(**kwargs):
785 """Decorate a function with new attributes."""
786 def decorate(function):
787 for k in kwargs:
788 setattr(function, k, kwargs[k])
789 return function
790 return decorate
791
792
793@no_args
794def CMDopened():
795 """Lists modified files in the current directory down."""
796 return ListFiles(False)
797
798
799@no_args
800def CMDstatus():
801 """Lists modified and unknown files in the current directory down."""
802 return ListFiles(True)
803
804
805@need_change_and_args
[email protected]2b9aa8e2010-08-25 20:01:42806@attrs(usage='[--no_presubmit] [--no_watchlists]')
[email protected]62fd6932010-05-27 13:13:23807def CMDupload(change_info, args):
808 """Uploads the changelist to the server for review.
809
[email protected]5db5ba52010-07-20 15:50:47810 This does not submit a try job; use gcl try to submit a try job.
[email protected]62fd6932010-05-27 13:13:23811 """
[email protected]10ccd112010-08-24 16:48:42812 if '-s' in args or '--server' in args:
813 ErrorExit('Don\'t use the -s flag, fix codereview.settings instead')
[email protected]17f59f22009-06-12 13:27:24814 if not change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42815 print "Nothing to upload, changelist is empty."
[email protected]35fe9ad2010-05-25 23:59:54816 return 0
[email protected]30becc22011-03-17 01:21:22817
818 output = OptionallyDoPresubmitChecks(change_info, False, args)
819 if not output.should_continue():
[email protected]35fe9ad2010-05-25 23:59:54820 return 1
[email protected]62fd6932010-05-27 13:13:23821 no_watchlists = (FilterFlag(args, "--no_watchlists") or
822 FilterFlag(args, "--no-watchlists"))
[email protected]fb2b8eb2009-04-23 21:03:42823
824 # Map --send-mail to --send_mail
[email protected]51ee0072009-06-08 19:20:05825 if FilterFlag(args, "--send-mail"):
[email protected]fb2b8eb2009-04-23 21:03:42826 args.append("--send_mail")
827
[email protected]420d3b82012-05-14 18:41:38828 # Replace -m with -t and --message with --title, but make sure to
829 # preserve anything after the -m/--message.
830 found_deprecated_arg = [False]
831 def replace_message(a):
832 if a.startswith('-m'):
833 found_deprecated_arg[0] = True
834 return '-t' + a[2:]
835 elif a.startswith('--message'):
836 found_deprecated_arg[0] = True
837 return '--title' + a[9:]
838 return a
839 args = map(replace_message, args)
840 if found_deprecated_arg[0]:
841 print >> sys.stderr, (
842 '\nWARNING: Use -t or --title to set the title of the patchset.\n'
843 'In the near future, -m or --message will send a message instead.\n'
844 'See http://goo.gl/JGg0Z for details.\n')
[email protected]25548ab2012-05-14 13:26:52845
[email protected]fb2b8eb2009-04-23 21:03:42846 upload_arg = ["upload.py", "-y"]
[email protected]bf1fdca2010-11-01 18:05:36847 upload_arg.append("--server=%s" % change_info.rietveld)
[email protected]30becc22011-03-17 01:21:22848
849 reviewers = change_info.reviewers or output.reviewers
850 if (reviewers and
851 not any(arg.startswith('-r') or arg.startswith('--reviewer') for
852 arg in args)):
853 upload_arg.append('--reviewers=%s' % ','.join(reviewers))
854
[email protected]fb2b8eb2009-04-23 21:03:42855 upload_arg.extend(args)
856
[email protected]f2407ef2012-01-23 19:13:37857 desc_file = None
[email protected]d79d4192011-12-19 22:25:44858 try:
[email protected]71e12a92012-02-14 02:34:15859 if change_info.issue:
860 # Uploading a new patchset.
[email protected]f2407ef2012-01-23 19:13:37861 upload_arg.append("--issue=%d" % change_info.issue)
[email protected]71e12a92012-02-14 02:34:15862
[email protected]25548ab2012-05-14 13:26:52863 if not any(i.startswith('--title') or i.startswith('-t') for i in args):
[email protected]71e12a92012-02-14 02:34:15864 upload_arg.append('--title= ')
865 else:
866 # First time we upload.
[email protected]f2407ef2012-01-23 19:13:37867 handle, desc_file = tempfile.mkstemp(text=True)
868 os.write(handle, change_info.description)
869 os.close(handle)
870
871 # Watchlist processing -- CC people interested in this changeset
872 # http://dev.chromium.org/developers/contributing-code/watchlists
873 if not no_watchlists:
874 import watchlists
875 watchlist = watchlists.Watchlists(change_info.GetLocalRoot())
876 watchers = watchlist.GetWatchersForPaths(change_info.GetFileNames())
877
878 cc_list = GetCodeReviewSetting("CC_LIST")
879 if not no_watchlists and watchers:
880 # Filter out all empty elements and join by ','
881 cc_list = ','.join(filter(None, [cc_list] + watchers))
882 if cc_list:
883 upload_arg.append("--cc=" + cc_list)
[email protected]71e12a92012-02-14 02:34:15884 upload_arg.append("--file=%s" % desc_file)
[email protected]f2407ef2012-01-23 19:13:37885
886 if GetCodeReviewSetting("PRIVATE") == "True":
887 upload_arg.append("--private")
888
889 # If we have a lot of files with long paths, then we won't be able to fit
890 # the command to "svn diff". Instead, we generate the diff manually for
891 # each file and concatenate them before passing it to upload.py.
892 if change_info.patch is None:
893 change_info.patch = GenerateDiff(change_info.GetFileNames())
894
895 # Change the current working directory before calling upload.py so that it
896 # shows the correct base.
897 previous_cwd = os.getcwd()
898 os.chdir(change_info.GetLocalRoot())
899 try:
900 try:
901 issue, patchset = upload.RealMain(upload_arg, change_info.patch)
902 except KeyboardInterrupt:
903 sys.exit(1)
904 if issue and patchset:
905 change_info.issue = int(issue)
906 change_info.patchset = int(patchset)
907 change_info.Save()
908 change_info.PrimeLint()
909 finally:
910 os.chdir(previous_cwd)
911 finally:
[email protected]d79d4192011-12-19 22:25:44912 if desc_file:
913 os.remove(desc_file)
[email protected]02287952010-07-23 22:36:58914 print "*** Upload does not submit a try; use gcl try to submit a try. ***"
[email protected]35fe9ad2010-05-25 23:59:54915 return 0
[email protected]fb2b8eb2009-04-23 21:03:42916
[email protected]fb2b8eb2009-04-23 21:03:42917
[email protected]8e13a092010-11-02 19:06:06918@need_change_and_args
919@attrs(usage='[--upload]')
920def CMDpresubmit(change_info, args):
[email protected]62fd6932010-05-27 13:13:23921 """Runs presubmit checks on the change.
922
923 The actual presubmit code is implemented in presubmit_support.py and looks
924 for PRESUBMIT.py files."""
[email protected]17f59f22009-06-12 13:27:24925 if not change_info.GetFiles():
[email protected]8e13a092010-11-02 19:06:06926 print('Nothing to presubmit check, changelist is empty.')
[email protected]4c22d722010-05-14 19:01:22927 return 0
[email protected]8e13a092010-11-02 19:06:06928 parser = optparse.OptionParser()
929 parser.add_option('--upload', action='store_true')
930 options, args = parser.parse_args(args)
931 if args:
932 parser.error('Unrecognized args: %s' % args)
933 if options.upload:
934 print('*** Presubmit checks for UPLOAD would report: ***')
935 return not DoPresubmitChecks(change_info, False, False)
936 else:
937 print('*** Presubmit checks for COMMIT would report: ***')
938 return not DoPresubmitChecks(change_info, True, False)
[email protected]fb2b8eb2009-04-23 21:03:42939
940
941def TryChange(change_info, args, swallow_exception):
942 """Create a diff file of change_info and send it to the try server."""
943 try:
944 import trychange
945 except ImportError:
946 if swallow_exception:
[email protected]35fe9ad2010-05-25 23:59:54947 return 1
[email protected]fb2b8eb2009-04-23 21:03:42948 ErrorExit("You need to install trychange.py to use the try server.")
949
[email protected]18111352009-12-20 17:21:28950 trychange_args = []
[email protected]fb2b8eb2009-04-23 21:03:42951 if change_info:
[email protected]18111352009-12-20 17:21:28952 trychange_args.extend(['--name', change_info.name])
[email protected]32ba2602009-06-06 18:44:48953 if change_info.issue:
954 trychange_args.extend(["--issue", str(change_info.issue)])
955 if change_info.patchset:
956 trychange_args.extend(["--patchset", str(change_info.patchset)])
[email protected]15169952011-09-27 14:30:53957 change = presubmit_support.SvnChange(change_info.name,
958 change_info.description,
959 change_info.GetLocalRoot(),
960 change_info.GetFiles(),
961 change_info.issue,
962 change_info.patchset,
963 None)
[email protected]fb2b8eb2009-04-23 21:03:42964 else:
[email protected]15169952011-09-27 14:30:53965 change = None
966
[email protected]d3e57542011-03-15 09:43:47967 trychange_args.extend(args)
[email protected]d0891922010-05-31 18:33:16968 return trychange.TryChange(
969 trychange_args,
[email protected]15169952011-09-27 14:30:53970 change=change,
[email protected]d0891922010-05-31 18:33:16971 swallow_exception=swallow_exception,
972 prog='gcl try',
973 extra_epilog='\n'
974 'When called from gcl, use the format gcl try <change_name>.\n')
[email protected]fb2b8eb2009-04-23 21:03:42975
976
[email protected]62fd6932010-05-27 13:13:23977@need_change_and_args
978@attrs(usage='[--no_presubmit]')
[email protected]4357af22010-05-27 15:42:34979def CMDcommit(change_info, args):
[email protected]62fd6932010-05-27 13:13:23980 """Commits the changelist to the repository."""
[email protected]17f59f22009-06-12 13:27:24981 if not change_info.GetFiles():
[email protected]fb2b8eb2009-04-23 21:03:42982 print "Nothing to commit, changelist is empty."
[email protected]4c22d722010-05-14 19:01:22983 return 1
[email protected]73ac0f12011-03-17 23:34:22984
985 output = OptionallyDoPresubmitChecks(change_info, True, args)
986 if not output.should_continue():
[email protected]4c22d722010-05-14 19:01:22987 return 1
[email protected]fb2b8eb2009-04-23 21:03:42988
[email protected]1bb04aa2009-06-01 17:52:11989 # We face a problem with svn here: Let's say change 'bleh' modifies
990 # svn:ignore on dir1\. but another unrelated change 'pouet' modifies
991 # dir1\foo.cc. When the user `gcl commit bleh`, foo.cc is *also committed*.
992 # The only fix is to use --non-recursive but that has its issues too:
993 # Let's say if dir1 is deleted, --non-recursive must *not* be used otherwise
994 # you'll get "svn: Cannot non-recursively commit a directory deletion of a
995 # directory with child nodes". Yay...
996 commit_cmd = ["svn", "commit"]
[email protected]fb2b8eb2009-04-23 21:03:42997 if change_info.issue:
998 # Get the latest description from Rietveld.
[email protected]bf1fdca2010-11-01 18:05:36999 change_info.description = change_info.GetIssueDescription()
[email protected]fb2b8eb2009-04-23 21:03:421000
1001 commit_message = change_info.description.replace('\r\n', '\n')
1002 if change_info.issue:
[email protected]bf1fdca2010-11-01 18:05:361003 server = change_info.rietveld
[email protected]fcff9272010-04-29 23:56:191004 if not server.startswith("http://") and not server.startswith("https://"):
1005 server = "http://" + server
1006 commit_message += ('\nReview URL: %s/%d' % (server, change_info.issue))
[email protected]fb2b8eb2009-04-23 21:03:421007
1008 handle, commit_filename = tempfile.mkstemp(text=True)
1009 os.write(handle, commit_message)
1010 os.close(handle)
[email protected]5d0fc9a2011-12-01 00:42:561011 try:
1012 handle, targets_filename = tempfile.mkstemp(text=True)
1013 os.write(handle, "\n".join(change_info.GetFileNames()))
1014 os.close(handle)
1015 try:
1016 commit_cmd += ['--file=' + commit_filename]
1017 commit_cmd += ['--targets=' + targets_filename]
1018 # Change the current working directory before calling commit.
[email protected]5d0fc9a2011-12-01 00:42:561019 output = ''
1020 try:
1021 output = RunShell(commit_cmd, True)
1022 except subprocess2.CalledProcessError, e:
1023 ErrorExit('Commit failed.\n%s' % e)
1024 finally:
1025 os.remove(commit_filename)
1026 finally:
1027 os.remove(targets_filename)
[email protected]fb2b8eb2009-04-23 21:03:421028 if output.find("Committed revision") != -1:
1029 change_info.Delete()
1030
1031 if change_info.issue:
1032 revision = re.compile(".*?\nCommitted revision (\d+)",
1033 re.DOTALL).match(output).group(1)
[email protected]eb5edbc2012-01-16 17:03:281034 viewvc_url = gclient_utils.UpgradeToHttps(GetCodeReviewSetting('VIEW_VC'))
[email protected]30becc22011-03-17 01:21:221035 change_info.description += '\n'
[email protected]fb2b8eb2009-04-23 21:03:421036 if viewvc_url:
1037 change_info.description += "\nCommitted: " + viewvc_url + revision
1038 change_info.CloseIssue()
[email protected]4c22d722010-05-14 19:01:221039 return 0
[email protected]fb2b8eb2009-04-23 21:03:421040
[email protected]2c8d4b22009-06-06 21:03:101041
[email protected]35fe9ad2010-05-25 23:59:541042def CMDchange(args):
[email protected]62fd6932010-05-27 13:13:231043 """Creates or edits a changelist.
1044
[email protected]d579fcf2011-12-13 20:36:031045 Only scans the current directory and subdirectories.
1046 """
1047 # Verify the user is running the change command from a read-write checkout.
1048 svn_info = SVN.CaptureLocalInfo([], '.')
1049 if not svn_info:
1050 ErrorExit("Current checkout is unversioned. Please retry with a versioned "
1051 "directory.")
1052
[email protected]35fe9ad2010-05-25 23:59:541053 if len(args) == 0:
1054 # Generate a random changelist name.
1055 changename = GenerateChangeName()
1056 elif args[0] == '--force':
1057 changename = GenerateChangeName()
1058 else:
1059 changename = args[0]
1060 change_info = ChangeInfo.Load(changename, GetRepositoryRoot(), False, True)
[email protected]d36b3ed2009-11-09 18:51:421061
[email protected]35fe9ad2010-05-25 23:59:541062 if len(args) == 2:
[email protected]c8f3cf82010-09-09 20:00:121063 if not os.path.isfile(args[1]):
1064 ErrorExit('The change "%s" doesn\'t exist.' % args[1])
[email protected]35fe9ad2010-05-25 23:59:541065 f = open(args[1], 'rU')
[email protected]9ce98222009-10-19 20:24:171066 override_description = f.read()
1067 f.close()
1068 else:
1069 override_description = None
[email protected]5aeb7dd2009-11-17 18:09:011070
[email protected]ea452b32009-11-22 20:04:311071 if change_info.issue and not change_info.NeedsUpload():
[email protected]fb2b8eb2009-04-23 21:03:421072 try:
[email protected]bf1fdca2010-11-01 18:05:361073 description = change_info.GetIssueDescription()
[email protected]fb2b8eb2009-04-23 21:03:421074 except urllib2.HTTPError, err:
1075 if err.code == 404:
1076 # The user deleted the issue in Rietveld, so forget the old issue id.
1077 description = change_info.description
[email protected]2c8d4b22009-06-06 21:03:101078 change_info.issue = 0
[email protected]fb2b8eb2009-04-23 21:03:421079 change_info.Save()
1080 else:
1081 ErrorExit("Error getting the description from Rietveld: " + err)
1082 else:
[email protected]85532fc2009-06-04 22:36:531083 if override_description:
1084 description = override_description
1085 else:
1086 description = change_info.description
[email protected]fb2b8eb2009-04-23 21:03:421087
1088 other_files = GetFilesNotInCL()
[email protected]bfd09ce2009-08-05 21:17:231089
[email protected]f0dfba32009-08-07 22:03:371090 # Edited files (as opposed to files with only changed properties) will have
1091 # a letter for the first character in the status string.
[email protected]85532fc2009-06-04 22:36:531092 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE)
[email protected]f0dfba32009-08-07 22:03:371093 affected_files = [x for x in other_files if file_re.match(x[0])]
1094 unaffected_files = [x for x in other_files if not file_re.match(x[0])]
[email protected]fb2b8eb2009-04-23 21:03:421095
[email protected]20254fc2011-03-22 18:28:591096 description = description.rstrip() + '\n'
[email protected]30becc22011-03-17 01:21:221097
[email protected]fb2b8eb2009-04-23 21:03:421098 separator1 = ("\n---All lines above this line become the description.\n"
[email protected]17f59f22009-06-12 13:27:241099 "---Repository Root: " + change_info.GetLocalRoot() + "\n"
[email protected]fb2b8eb2009-04-23 21:03:421100 "---Paths in this changelist (" + change_info.name + "):\n")
1101 separator2 = "\n\n---Paths modified but not in any changelist:\n\n"
[email protected]f1c2f432011-09-19 23:29:081102
[email protected]0e0436a2011-10-25 13:32:411103 text = (description + separator1 + '\n' +
[email protected]20254fc2011-03-22 18:28:591104 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()]))
[email protected]f0dfba32009-08-07 22:03:371105
1106 if change_info.Exists():
[email protected]20254fc2011-03-22 18:28:591107 text += (separator2 +
1108 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n')
[email protected]f0dfba32009-08-07 22:03:371109 else:
[email protected]20254fc2011-03-22 18:28:591110 text += ('\n'.join([f[0] + f[1] for f in affected_files]) + '\n' +
1111 separator2)
1112 text += '\n'.join([f[0] + f[1] for f in unaffected_files]) + '\n'
[email protected]fb2b8eb2009-04-23 21:03:421113
[email protected]0e0436a2011-10-25 13:32:411114 result = gclient_utils.RunEditor(text, False)
[email protected]fb2b8eb2009-04-23 21:03:421115 if not result:
[email protected]0e0436a2011-10-25 13:32:411116 ErrorExit('Running editor failed')
[email protected]fb2b8eb2009-04-23 21:03:421117
1118 split_result = result.split(separator1, 1)
1119 if len(split_result) != 2:
[email protected]0e0436a2011-10-25 13:32:411120 ErrorExit("Don't modify the text starting with ---!\n\n%r" % result)
[email protected]fb2b8eb2009-04-23 21:03:421121
[email protected]ea452b32009-11-22 20:04:311122 # Update the CL description if it has changed.
[email protected]fb2b8eb2009-04-23 21:03:421123 new_description = split_result[0]
1124 cl_files_text = split_result[1]
[email protected]20254fc2011-03-22 18:28:591125 if new_description != description or override_description:
1126 change_info.description = new_description
[email protected]ea452b32009-11-22 20:04:311127 change_info.needs_upload = True
[email protected]fb2b8eb2009-04-23 21:03:421128
1129 new_cl_files = []
1130 for line in cl_files_text.splitlines():
1131 if not len(line):
1132 continue
1133 if line.startswith("---"):
1134 break
1135 status = line[:7]
[email protected]e3608df2009-11-10 20:22:571136 filename = line[7:]
1137 new_cl_files.append((status, filename))
[email protected]bfd09ce2009-08-05 21:17:231138
[email protected]3a174252010-10-29 15:54:511139 if (not len(change_info.GetFiles()) and not change_info.issue and
[email protected]20254fc2011-03-22 18:28:591140 not len(new_description) and not new_cl_files):
[email protected]bfd09ce2009-08-05 21:17:231141 ErrorExit("Empty changelist not saved")
1142
[email protected]17f59f22009-06-12 13:27:241143 change_info._files = new_cl_files
[email protected]fb2b8eb2009-04-23 21:03:421144 change_info.Save()
[email protected]53bcf152009-11-13 21:04:101145 if svn_info.get('URL', '').startswith('http:'):
[email protected]f9b1fdb2012-04-08 17:32:451146 Warn("WARNING: Creating CL in a read-only checkout. You will need to "
1147 "commit using a commit queue!")
[email protected]53bcf152009-11-13 21:04:101148
[email protected]fb2b8eb2009-04-23 21:03:421149 print change_info.name + " changelist saved."
1150 if change_info.MissingTests():
1151 Warn("WARNING: " + MISSING_TEST_MSG)
1152
[email protected]ea452b32009-11-22 20:04:311153 # Update the Rietveld issue.
1154 if change_info.issue and change_info.NeedsUpload():
1155 change_info.UpdateRietveldDescription()
1156 change_info.needs_upload = False
1157 change_info.Save()
[email protected]4c22d722010-05-14 19:01:221158 return 0
[email protected]ea452b32009-11-22 20:04:311159
1160
[email protected]62fd6932010-05-27 13:13:231161@need_change_and_args
1162def CMDlint(change_info, args):
1163 """Runs cpplint.py on all the files in the change list.
1164
1165 Checks all the files in the changelist for possible style violations.
1166 """
[email protected]d79d4192011-12-19 22:25:441167 # Access to a protected member _XX of a client class
1168 # pylint: disable=W0212
[email protected]fb2b8eb2009-04-23 21:03:421169 try:
1170 import cpplint
[email protected]8b8d8be2011-09-08 15:34:451171 import cpplint_chromium
[email protected]fb2b8eb2009-04-23 21:03:421172 except ImportError:
1173 ErrorExit("You need to install cpplint.py to lint C++ files.")
[email protected]fb2b8eb2009-04-23 21:03:421174 # Change the current working directory before calling lint so that it
1175 # shows the correct base.
1176 previous_cwd = os.getcwd()
[email protected]17f59f22009-06-12 13:27:241177 os.chdir(change_info.GetLocalRoot())
[email protected]d79d4192011-12-19 22:25:441178 try:
1179 # Process cpplints arguments if any.
1180 filenames = cpplint.ParseArguments(args + change_info.GetFileNames())
[email protected]fb2b8eb2009-04-23 21:03:421181
[email protected]d79d4192011-12-19 22:25:441182 white_list = GetCodeReviewSetting("LINT_REGEX")
1183 if not white_list:
1184 white_list = DEFAULT_LINT_REGEX
1185 white_regex = re.compile(white_list)
1186 black_list = GetCodeReviewSetting("LINT_IGNORE_REGEX")
1187 if not black_list:
1188 black_list = DEFAULT_LINT_IGNORE_REGEX
1189 black_regex = re.compile(black_list)
1190 extra_check_functions = [cpplint_chromium.CheckPointerDeclarationWhitespace]
1191 for filename in filenames:
1192 if white_regex.match(filename):
1193 if black_regex.match(filename):
1194 print "Ignoring file %s" % filename
1195 else:
1196 cpplint.ProcessFile(filename, cpplint._cpplint_state.verbose_level,
1197 extra_check_functions)
[email protected]fb2b8eb2009-04-23 21:03:421198 else:
[email protected]d79d4192011-12-19 22:25:441199 print "Skipping file %s" % filename
1200 finally:
1201 os.chdir(previous_cwd)
[email protected]fb2b8eb2009-04-23 21:03:421202
1203 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count
[email protected]4c22d722010-05-14 19:01:221204 return 1
[email protected]fb2b8eb2009-04-23 21:03:421205
1206
[email protected]b0dfd352009-06-10 14:12:541207def DoPresubmitChecks(change_info, committing, may_prompt):
[email protected]fb2b8eb2009-04-23 21:03:421208 """Imports presubmit, then calls presubmit.DoPresubmitChecks."""
[email protected]0ff1fab2009-05-22 13:08:151209 root_presubmit = GetCachedFile('PRESUBMIT.py', use_root=True)
[email protected]2e501802009-06-12 22:00:411210 change = presubmit_support.SvnChange(change_info.name,
1211 change_info.description,
1212 change_info.GetLocalRoot(),
1213 change_info.GetFiles(),
1214 change_info.issue,
[email protected]58407af2011-04-12 23:15:571215 change_info.patchset,
1216 None)
[email protected]cab38e92011-04-09 00:30:511217 output = presubmit_support.DoPresubmitChecks(
1218 change=change,
1219 committing=committing,
1220 verbose=False,
1221 output_stream=sys.stdout,
1222 input_stream=sys.stdin,
1223 default_presubmit=root_presubmit,
1224 may_prompt=may_prompt,
[email protected]239f4112011-06-03 20:08:231225 rietveld_obj=change_info.RpcServer())
[email protected]5ac21012011-03-16 02:58:251226 if not output.should_continue() and may_prompt:
1227 # TODO(dpranke): move into DoPresubmitChecks(), unify cmd line args.
[email protected]fb2b8eb2009-04-23 21:03:421228 print "\nPresubmit errors, can't continue (use --no_presubmit to bypass)"
[email protected]5ac21012011-03-16 02:58:251229
[email protected]30becc22011-03-17 01:21:221230 return output
[email protected]fb2b8eb2009-04-23 21:03:421231
1232
[email protected]62fd6932010-05-27 13:13:231233@no_args
1234def CMDchanges():
[email protected]4c22d722010-05-14 19:01:221235 """Lists all the changelists and their files."""
[email protected]fb2b8eb2009-04-23 21:03:421236 for cl in GetCLs():
[email protected]8d5c9a52009-06-12 15:59:081237 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
[email protected]fb2b8eb2009-04-23 21:03:421238 print "\n--- Changelist " + change_info.name + ":"
[email protected]e3608df2009-11-10 20:22:571239 for filename in change_info.GetFiles():
1240 print "".join(filename)
[email protected]4c22d722010-05-14 19:01:221241 return 0
[email protected]fb2b8eb2009-04-23 21:03:421242
1243
[email protected]62fd6932010-05-27 13:13:231244@no_args
1245def CMDdeleteempties():
[email protected]bfd09ce2009-08-05 21:17:231246 """Delete all changelists that have no files."""
1247 print "\n--- Deleting:"
1248 for cl in GetCLs():
1249 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
[email protected]3a174252010-10-29 15:54:511250 if not len(change_info.GetFiles()):
[email protected]bfd09ce2009-08-05 21:17:231251 print change_info.name
1252 change_info.Delete()
[email protected]4c22d722010-05-14 19:01:221253 return 0
1254
1255
[email protected]62fd6932010-05-27 13:13:231256@no_args
1257def CMDnothave():
[email protected]4c22d722010-05-14 19:01:221258 """Lists files unknown to Subversion."""
[email protected]62fd6932010-05-27 13:13:231259 for filename in UnknownFiles():
[email protected]4c22d722010-05-14 19:01:221260 print "? " + "".join(filename)
1261 return 0
1262
1263
[email protected]62fd6932010-05-27 13:13:231264@attrs(usage='<svn options>')
[email protected]35fe9ad2010-05-25 23:59:541265def CMDdiff(args):
[email protected]62fd6932010-05-27 13:13:231266 """Diffs all files in the changelist or all files that aren't in a CL."""
[email protected]35fe9ad2010-05-25 23:59:541267 files = None
1268 if args:
[email protected]62fd6932010-05-27 13:13:231269 change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
[email protected]35fe9ad2010-05-25 23:59:541270 files = change_info.GetFileNames()
1271 else:
[email protected]707c1482010-06-02 19:52:421272 files = [f[1] for f in GetFilesNotInCL()]
[email protected]38729702010-06-01 23:42:031273
1274 root = GetRepositoryRoot()
1275 cmd = ['svn', 'diff']
1276 cmd.extend([os.path.join(root, x) for x in files])
1277 cmd.extend(args)
[email protected]97335082011-10-20 15:00:161278 return RunShellWithReturnCode(cmd, print_output=True)[1]
[email protected]4c22d722010-05-14 19:01:221279
1280
[email protected]62fd6932010-05-27 13:13:231281@no_args
1282def CMDsettings():
1283 """Prints code review settings for this checkout."""
[email protected]4c22d722010-05-14 19:01:221284 # Force load settings
[email protected]6e29d572010-06-04 17:32:201285 GetCodeReviewSetting("UNKNOWN")
[email protected]4c22d722010-05-14 19:01:221286 del CODEREVIEW_SETTINGS['__just_initialized']
1287 print '\n'.join(("%s: %s" % (str(k), str(v))
1288 for (k,v) in CODEREVIEW_SETTINGS.iteritems()))
1289 return 0
1290
1291
[email protected]35fe9ad2010-05-25 23:59:541292@need_change
1293def CMDdescription(change_info):
[email protected]4c22d722010-05-14 19:01:221294 """Prints the description of the specified change to stdout."""
[email protected]4c22d722010-05-14 19:01:221295 print change_info.description
1296 return 0
1297
1298
[email protected]79b7ef02010-11-01 13:25:131299def CMDdelete(args):
[email protected]4c22d722010-05-14 19:01:221300 """Deletes a changelist."""
[email protected]79b7ef02010-11-01 13:25:131301 if not len(args) == 1:
1302 ErrorExit('You need to pass a change list name')
[email protected]a2d7edf2011-04-04 17:53:451303 filepath = GetChangelistInfoFile(args[0])
1304 if not os.path.isfile(filepath):
1305 ErrorExit('You need to pass a valid change list name')
1306 os.remove(filepath)
[email protected]4c22d722010-05-14 19:01:221307 return 0
1308
1309
[email protected]35fe9ad2010-05-25 23:59:541310def CMDtry(args):
[email protected]62fd6932010-05-27 13:13:231311 """Sends the change to the tryserver to do a test run on your code.
[email protected]4c22d722010-05-14 19:01:221312
1313 To send multiple changes as one path, use a comma-separated list of
1314 changenames. Use 'gcl help try' for more information!"""
1315 # When the change contains no file, send the "changename" positional
1316 # argument to trychange.py.
[email protected]35fe9ad2010-05-25 23:59:541317 # When the command is 'try' and --patchset is used, the patch to try
1318 # is on the Rietveld server.
1319 if not args:
1320 ErrorExit("You need to pass a change list name")
1321 if args[0].find(',') != -1:
1322 change_info = LoadChangelistInfoForMultiple(args[0], GetRepositoryRoot(),
1323 True, True)
1324 else:
1325 change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(),
[email protected]53790112011-11-29 20:36:161326 True, True)
[email protected]4c22d722010-05-14 19:01:221327 if change_info.GetFiles():
[email protected]35fe9ad2010-05-25 23:59:541328 args = args[1:]
[email protected]4c22d722010-05-14 19:01:221329 else:
1330 change_info = None
[email protected]35fe9ad2010-05-25 23:59:541331 return TryChange(change_info, args, swallow_exception=False)
[email protected]4c22d722010-05-14 19:01:221332
1333
[email protected]62fd6932010-05-27 13:13:231334@attrs(usage='<old-name> <new-name>')
[email protected]35fe9ad2010-05-25 23:59:541335def CMDrename(args):
[email protected]4c22d722010-05-14 19:01:221336 """Renames an existing change."""
[email protected]35fe9ad2010-05-25 23:59:541337 if len(args) != 2:
[email protected]4c22d722010-05-14 19:01:221338 ErrorExit("Usage: gcl rename <old-name> <new-name>.")
[email protected]35fe9ad2010-05-25 23:59:541339 src, dst = args
[email protected]4c22d722010-05-14 19:01:221340 src_file = GetChangelistInfoFile(src)
1341 if not os.path.isfile(src_file):
1342 ErrorExit("Change '%s' does not exist." % src)
1343 dst_file = GetChangelistInfoFile(dst)
1344 if os.path.isfile(dst_file):
1345 ErrorExit("Change '%s' already exists; pick a new name." % dst)
1346 os.rename(src_file, dst_file)
1347 print "Change '%s' renamed '%s'." % (src, dst)
1348 return 0
[email protected]bfd09ce2009-08-05 21:17:231349
1350
[email protected]35fe9ad2010-05-25 23:59:541351def CMDpassthru(args):
[email protected]62fd6932010-05-27 13:13:231352 """Everything else that is passed into gcl we redirect to svn.
1353
1354 It assumes a change list name is passed and is converted with the files names.
1355 """
[email protected]f639e632011-10-28 00:25:571356 if not args or len(args) < 2:
1357 ErrorExit("You need to pass a change list name for this svn fall-through "
1358 "command")
[email protected]3f49e2b2011-10-27 15:45:331359 cl_name = args[1]
[email protected]35fe9ad2010-05-25 23:59:541360 args = ["svn", args[0]]
1361 if len(args) > 1:
1362 root = GetRepositoryRoot()
[email protected]3f49e2b2011-10-27 15:45:331363 change_info = ChangeInfo.Load(cl_name, root, True, True)
[email protected]35fe9ad2010-05-25 23:59:541364 args.extend([os.path.join(root, x) for x in change_info.GetFileNames()])
[email protected]97335082011-10-20 15:00:161365 return RunShellWithReturnCode(args, print_output=True)[1]
[email protected]35fe9ad2010-05-25 23:59:541366
1367
[email protected]62fd6932010-05-27 13:13:231368def Command(name):
1369 return getattr(sys.modules[__name__], 'CMD' + name, None)
1370
1371
1372def GenUsage(command):
1373 """Modify an OptParse object with the function's documentation."""
1374 obj = Command(command)
1375 display = command
1376 more = getattr(obj, 'usage', '')
1377 if command == 'help':
1378 display = '<command>'
[email protected]3a174252010-10-29 15:54:511379 need_change_val = ''
[email protected]62fd6932010-05-27 13:13:231380 if getattr(obj, 'need_change', None):
[email protected]3a174252010-10-29 15:54:511381 need_change_val = ' <change_list>'
[email protected]62fd6932010-05-27 13:13:231382 options = ' [options]'
1383 if getattr(obj, 'no_args', None):
1384 options = ''
[email protected]3a174252010-10-29 15:54:511385 res = 'Usage: gcl %s%s%s %s\n\n' % (display, need_change_val, options, more)
[email protected]62fd6932010-05-27 13:13:231386 res += re.sub('\n ', '\n', obj.__doc__)
1387 return res
1388
1389
1390def CMDhelp(args):
1391 """Prints this help or help for the given command."""
1392 if args and 'CMD' + args[0] in dir(sys.modules[__name__]):
1393 print GenUsage(args[0])
1394
1395 # These commands defer to external tools so give this info too.
1396 if args[0] == 'try':
1397 TryChange(None, ['--help'], swallow_exception=False)
1398 if args[0] == 'upload':
1399 upload.RealMain(['upload.py', '--help'])
1400 return 0
1401
1402 print GenUsage('help')
1403 print sys.modules[__name__].__doc__
1404 print 'version ' + __version__ + '\n'
1405
1406 print('Commands are:\n' + '\n'.join([
1407 ' %-12s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1408 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1409 return 0
1410
1411
[email protected]35fe9ad2010-05-25 23:59:541412def main(argv):
[email protected]82798cb2012-02-23 18:16:121413 if sys.hexversion < 0x02060000:
[email protected]c3a15a22010-11-20 03:12:271414 print >> sys.stderr, (
[email protected]82798cb2012-02-23 18:16:121415 '\nYour python version %s is unsupported, please upgrade.\n' %
1416 sys.version.split(' ', 1)[0])
1417 return 2
[email protected]c68f9cb2010-06-17 20:34:181418 if not argv:
1419 argv = ['help']
1420 command = Command(argv[0])
1421 # Help can be run from anywhere.
1422 if command == CMDhelp:
1423 return command(argv[1:])
1424
[email protected]a05be0b2009-06-30 19:13:021425 try:
[email protected]62fd6932010-05-27 13:13:231426 GetRepositoryRoot()
[email protected]31cb48a2011-04-04 18:01:361427 except (gclient_utils.Error, subprocess2.CalledProcessError):
[email protected]58c19382010-09-22 19:53:591428 print >> sys.stderr, 'To use gcl, you need to be in a subversion checkout.'
[email protected]62fd6932010-05-27 13:13:231429 return 1
1430
1431 # Create the directories where we store information about changelists if it
1432 # doesn't exist.
[email protected]807c4462010-07-10 00:45:281433 try:
1434 if not os.path.exists(GetInfoDir()):
1435 os.mkdir(GetInfoDir())
1436 if not os.path.exists(GetChangesDir()):
1437 os.mkdir(GetChangesDir())
1438 if not os.path.exists(GetCacheDir()):
1439 os.mkdir(GetCacheDir())
[email protected]fb2b8eb2009-04-23 21:03:421440
[email protected]807c4462010-07-10 00:45:281441 if command:
1442 return command(argv[1:])
1443 # Unknown command, try to pass that to svn
1444 return CMDpassthru(argv)
[email protected]31cb48a2011-04-04 18:01:361445 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
[email protected]58c19382010-09-22 19:53:591446 print >> sys.stderr, 'Got an exception'
1447 print >> sys.stderr, str(e)
1448 return 1
[email protected]42c7f662010-10-06 23:52:461449 except upload.ClientLoginError, e:
1450 print >> sys.stderr, 'Got an exception logging in to Rietveld'
1451 print >> sys.stderr, str(e)
[email protected]d579fcf2011-12-13 20:36:031452 return 1
[email protected]58c19382010-09-22 19:53:591453 except urllib2.HTTPError, e:
1454 if e.code != 500:
1455 raise
1456 print >> sys.stderr, (
1457 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
[email protected]7633e472010-09-22 20:03:141458 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))
[email protected]58c19382010-09-22 19:53:591459 return 1
1460
[email protected]fb2b8eb2009-04-23 21:03:421461
1462if __name__ == "__main__":
[email protected]35625c72011-03-23 17:34:021463 fix_encoding.fix_encoding()
[email protected]35fe9ad2010-05-25 23:59:541464 sys.exit(main(sys.argv[1:]))