blob: 1a440731a92575ef11442bd7719f0026bd551660 [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
2#
3# Copyright 2008 Google Inc. All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""A wrapper script to manage a set of client modules in different SCM.
18
19This script is intended to be used to help basic management of client
20program sources residing in one or more Subversion modules, along with
21other modules it depends on, also in Subversion, but possibly on
22multiple respositories, making a wrapper system apparently necessary.
23
24Files
25 .gclient : Current client configuration, written by 'config' command.
26 Format is a Python script defining 'solutions', a list whose
27 entries each are maps binding the strings "name" and "url"
28 to strings specifying the name and location of the client
29 module, as well as "custom_deps" to a map similar to the DEPS
30 file below.
31 .gclient_entries : A cache constructed by 'update' command. Format is a
32 Python script defining 'entries', a list of the names
33 of all modules in the client
34 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
35 submodule name to a URL where it can be found (via one SCM)
36
37Hooks
38 .gclient and DEPS files may optionally contain a list named "hooks" to
39 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000040 working copy as a result of a "sync"/"update" or "revert" operation. This
41 could be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000042 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000043 --force, all known hooks will run regardless of the state of the working
44 copy.
45
46 Each item in a "hooks" list is a dict, containing these two keys:
47 "pattern" The associated value is a string containing a regular
48 expression. When a file whose pathname matches the expression
49 is checked out, updated, or reverted, the hook's "action" will
50 run.
51 "action" A list describing a command to run along with its arguments, if
52 any. An action command will run at most one time per gclient
53 invocation, regardless of how many files matched the pattern.
54 The action is executed in the same directory as the .gclient
55 file. If the first item in the list is the string "python",
56 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000057 to run the command. If the list contains string "$matching_files"
58 it will be removed from the list and the list will be extended
59 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060
61 Example:
62 hooks = [
63 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
64 "action": ["python", "image_indexer.py", "--all"]},
65 ]
66"""
67
68__author__ = "darinf@gmail.com (Darin Fisher)"
maruel@chromium.org5df6a462009-08-28 18:52:26 +000069__version__ = "0.3.3"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070
71import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000072import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073import optparse
74import os
75import re
76import stat
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000077import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079import urllib
80
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000081import gclient_scm
82import gclient_utils
83from gclient_utils import Error, FileRead, FileWrite
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084
85# default help text
86DEFAULT_USAGE_TEXT = (
87"""usage: %prog <subcommand> [options] [--] [svn options/args...]
88a wrapper for managing a set of client modules in svn.
89Version """ + __version__ + """
90
91subcommands:
92 cleanup
93 config
94 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000095 export
kbr@google.comab318592009-09-04 00:54:55 +000096 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097 revert
98 status
99 sync
100 update
101 runhooks
102 revinfo
103
104Options and extra arguments can be passed to invoked svn commands by
105appending them to the command line. Note that if the first such
106appended option starts with a dash (-) then the options must be
107preceded by -- to distinguish them from gclient options.
108
109For additional help on a subcommand or examples of usage, try
110 %prog help <subcommand>
111 %prog help files
112""")
113
114GENERIC_UPDATE_USAGE_TEXT = (
115 """Perform a checkout/update of the modules specified by the gclient
116configuration; see 'help config'. Unless --revision is specified,
117then the latest revision of the root solutions is checked out, with
118dependent submodule versions updated according to DEPS files.
119If --revision is specified, then the given revision is used in place
120of the latest, either for a single solution or for all solutions.
121Unless the --force option is provided, solutions and modules whose
122local revision matches the one to update (i.e., they have not changed
evan@chromium.org67820ef2009-07-27 17:23:00 +0000123in the repository) are *not* modified. Unless --nohooks is provided,
124the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000125This a synonym for 'gclient %(alias)s'
126
127usage: gclient %(cmd)s [options] [--] [svn update options/args]
128
129Valid options:
130 --force : force update even for unchanged modules
evan@chromium.org67820ef2009-07-27 17:23:00 +0000131 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000132 --revision REV : update/checkout all solutions with specified revision
133 --revision SOLUTION@REV : update given solution to specified revision
134 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
135 --verbose : output additional diagnostics
maruel@chromium.orgb8b6b872009-06-30 18:50:56 +0000136 --head : update to latest revision, instead of last good revision
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000137
138Examples:
139 gclient %(cmd)s
140 update files from SVN according to current configuration,
141 *for modules which have changed since last update or sync*
142 gclient %(cmd)s --force
143 update files from SVN according to current configuration, for
144 all modules (useful for recovering files deleted from local copy)
145""")
146
147COMMAND_USAGE_TEXT = {
148 "cleanup":
149 """Clean up all working copies, using 'svn cleanup' for each module.
150Additional options and args may be passed to 'svn cleanup'.
151
152usage: cleanup [options] [--] [svn cleanup args/options]
153
154Valid options:
155 --verbose : output additional diagnostics
156""",
157 "config": """Create a .gclient file in the current directory; this
158specifies the configuration for further commands. After update/sync,
159top-level DEPS files in each module are read to determine dependent
160modules to operate on as well. If optional [url] parameter is
161provided, then configuration is read from a specified Subversion server
162URL. Otherwise, a --spec option must be provided.
163
164usage: config [option | url] [safesync url]
165
166Valid options:
167 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
168 *Note that due to Cygwin/Python brokenness, it
169 probably can't contain any newlines.*
170
171Examples:
172 gclient config https://gclient.googlecode.com/svn/trunk/gclient
173 configure a new client to check out gclient.py tool sources
174 gclient config --spec='solutions=[{"name":"gclient","""
175 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
176 '"custom_deps":{}}]',
177 "diff": """Display the differences between two revisions of modules.
178(Does 'svn diff' for each checked out module and dependences.)
179Additional args and options to 'svn diff' can be passed after
180gclient options.
181
182usage: diff [options] [--] [svn args/options]
183
184Valid options:
185 --verbose : output additional diagnostics
186
187Examples:
188 gclient diff
189 simple 'svn diff' for configured client and dependences
190 gclient diff -- -x -b
191 use 'svn diff -x -b' to suppress whitespace-only differences
192 gclient diff -- -r HEAD -x -b
193 diff versus the latest version of each module
194""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000195 "export":
196 """Wrapper for svn export for all managed directories
197""",
kbr@google.comab318592009-09-04 00:54:55 +0000198 "pack":
199
200 """Generate a patch which can be applied at the root of the tree.
201Internally, runs 'svn diff' on each checked out module and
202dependencies, and performs minimal postprocessing of the output. The
203resulting patch is printed to stdout and can be applied to a freshly
204checked out tree via 'patch -p0 < patchfile'. Additional args and
205options to 'svn diff' can be passed after gclient options.
206
207usage: pack [options] [--] [svn args/options]
208
209Valid options:
210 --verbose : output additional diagnostics
211
212Examples:
213 gclient pack > patch.txt
214 generate simple patch for configured client and dependences
215 gclient pack -- -x -b > patch.txt
216 generate patch using 'svn diff -x -b' to suppress
217 whitespace-only differences
218 gclient pack -- -r HEAD -x -b > patch.txt
219 generate patch, diffing each file versus the latest version of
220 each module
221""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000222 "revert":
223 """Revert every file in every managed directory in the client view.
224
225usage: revert
226""",
227 "status":
228 """Show the status of client and dependent modules, using 'svn diff'
229for each module. Additional options and args may be passed to 'svn diff'.
230
231usage: status [options] [--] [svn diff args/options]
232
233Valid options:
234 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000235 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000236""",
237 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
238 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
239 "help": """Describe the usage of this program or its subcommands.
240
241usage: help [options] [subcommand]
242
243Valid options:
244 --verbose : output additional diagnostics
245""",
246 "runhooks":
247 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000248according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000249
250usage: runhooks [options]
251
252Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000253 --verbose : output additional diagnostics
254""",
255 "revinfo":
256 """Outputs source path, server URL and revision information for every
257dependency in all solutions (no local checkout required).
258
259usage: revinfo [options]
260""",
261}
262
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000263DEFAULT_CLIENT_FILE_TEXT = ("""\
264# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000265# that will be checked out into your working copy. Each solution may
266# optionally define additional dependencies (via its DEPS file) to be
267# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000268# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000269# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000270# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000271# a text file which contains nothing but the last known good SCM revision to
272# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000273
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000274solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000275 { "name" : "%(solution_name)s",
276 "url" : "%(solution_url)s",
277 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000278 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000279 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000280 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000281 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000282 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000283 "safesync_url": "%(safesync_url)s"
284 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000285]
286""")
287
288
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000289## GClient implementation.
290
291
292class GClient(object):
293 """Object that represent a gclient checkout."""
294
295 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000296 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
297 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000298 ]
299
300 def __init__(self, root_dir, options):
301 self._root_dir = root_dir
302 self._options = options
303 self._config_content = None
304 self._config_dict = {}
305 self._deps_hooks = []
306
307 def SetConfig(self, content):
308 self._config_dict = {}
309 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000310 try:
311 exec(content, self._config_dict)
312 except SyntaxError, e:
313 try:
314 # Try to construct a human readable error message
315 error_message = [
316 'There is a syntax error in your configuration file.',
317 'Line #%s, character %s:' % (e.lineno, e.offset),
318 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
319 except:
320 # Something went wrong, re-raise the original exception
321 raise e
322 else:
323 # Raise a new exception with the human readable message:
324 raise Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000325
326 def SaveConfig(self):
327 FileWrite(os.path.join(self._root_dir, self._options.config_filename),
328 self._config_content)
329
330 def _LoadConfig(self):
331 client_source = FileRead(os.path.join(self._root_dir,
332 self._options.config_filename))
333 self.SetConfig(client_source)
334
335 def ConfigContent(self):
336 return self._config_content
337
338 def GetVar(self, key, default=None):
339 return self._config_dict.get(key, default)
340
341 @staticmethod
342 def LoadCurrentConfig(options, from_dir=None):
343 """Searches for and loads a .gclient file relative to the current working
344 dir.
345
346 Returns:
347 A dict representing the contents of the .gclient file or an empty dict if
348 the .gclient file doesn't exist.
349 """
350 if not from_dir:
351 from_dir = os.curdir
352 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000353 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000354 next = os.path.split(path)
355 if not next[1]:
356 return None
357 path = next[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000358 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000359 client._LoadConfig()
360 return client
361
362 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000363 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
364 'solution_name': solution_name,
365 'solution_url': solution_url,
366 'safesync_url' : safesync_url,
367 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000368
369 def _SaveEntries(self, entries):
370 """Creates a .gclient_entries file to record the list of unique checkouts.
371
372 The .gclient_entries file lives in the same directory as .gclient.
373
374 Args:
375 entries: A sequence of solution names.
376 """
377 text = "entries = [\n"
378 for entry in entries:
379 text += " \"%s\",\n" % entry
380 text += "]\n"
381 FileWrite(os.path.join(self._root_dir, self._options.entries_filename),
382 text)
383
384 def _ReadEntries(self):
385 """Read the .gclient_entries file for the given client.
386
387 Args:
388 client: The client for which the entries file should be read.
389
390 Returns:
391 A sequence of solution names, which will be empty if there is the
392 entries file hasn't been created yet.
393 """
394 scope = {}
395 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000396 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000397 return []
398 exec(FileRead(filename), scope)
399 return scope["entries"]
400
401 class FromImpl:
402 """Used to implement the From syntax."""
403
404 def __init__(self, module_name):
405 self.module_name = module_name
406
407 def __str__(self):
408 return 'From("%s")' % self.module_name
409
410 class _VarImpl:
411 def __init__(self, custom_vars, local_scope):
412 self._custom_vars = custom_vars
413 self._local_scope = local_scope
414
415 def Lookup(self, var_name):
416 """Implements the Var syntax."""
417 if var_name in self._custom_vars:
418 return self._custom_vars[var_name]
419 elif var_name in self._local_scope.get("vars", {}):
420 return self._local_scope["vars"][var_name]
421 raise Error("Var is not defined: %s" % var_name)
422
423 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
424 custom_vars):
425 """Parses the DEPS file for the specified solution.
426
427 Args:
428 solution_name: The name of the solution to query.
429 solution_deps_content: Content of the DEPS file for the solution
430 custom_vars: A dict of vars to override any vars defined in the DEPS file.
431
432 Returns:
433 A dict mapping module names (as relative paths) to URLs or an empty
434 dict if the solution does not have a DEPS file.
435 """
436 # Skip empty
437 if not solution_deps_content:
438 return {}
439 # Eval the content
440 local_scope = {}
441 var = self._VarImpl(custom_vars, local_scope)
442 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
443 exec(solution_deps_content, global_scope, local_scope)
444 deps = local_scope.get("deps", {})
445
446 # load os specific dependencies if defined. these dependencies may
447 # override or extend the values defined by the 'deps' member.
448 if "deps_os" in local_scope:
449 deps_os_choices = {
450 "win32": "win",
451 "win": "win",
452 "cygwin": "win",
453 "darwin": "mac",
454 "mac": "mac",
455 "unix": "unix",
456 "linux": "unix",
457 "linux2": "unix",
458 }
459
460 if self._options.deps_os is not None:
461 deps_to_include = self._options.deps_os.split(",")
462 if "all" in deps_to_include:
463 deps_to_include = deps_os_choices.values()
464 else:
465 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
466
467 deps_to_include = set(deps_to_include)
468 for deps_os_key in deps_to_include:
469 os_deps = local_scope["deps_os"].get(deps_os_key, {})
470 if len(deps_to_include) > 1:
471 # Ignore any overrides when including deps for more than one
472 # platform, so we collect the broadest set of dependencies available.
473 # We may end up with the wrong revision of something for our
474 # platform, but this is the best we can do.
475 deps.update([x for x in os_deps.items() if not x[0] in deps])
476 else:
477 deps.update(os_deps)
478
479 if 'hooks' in local_scope:
480 self._deps_hooks.extend(local_scope['hooks'])
481
482 # If use_relative_paths is set in the DEPS file, regenerate
483 # the dictionary using paths relative to the directory containing
484 # the DEPS file.
485 if local_scope.get('use_relative_paths'):
486 rel_deps = {}
487 for d, url in deps.items():
488 # normpath is required to allow DEPS to use .. in their
489 # dependency local path.
490 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
491 return rel_deps
492 else:
493 return deps
494
495 def _ParseAllDeps(self, solution_urls, solution_deps_content):
496 """Parse the complete list of dependencies for the client.
497
498 Args:
499 solution_urls: A dict mapping module names (as relative paths) to URLs
500 corresponding to the solutions specified by the client. This parameter
501 is passed as an optimization.
502 solution_deps_content: A dict mapping module names to the content
503 of their DEPS files
504
505 Returns:
506 A dict mapping module names (as relative paths) to URLs corresponding
507 to the entire set of dependencies to checkout for the given client.
508
509 Raises:
510 Error: If a dependency conflicts with another dependency or of a solution.
511 """
512 deps = {}
513 for solution in self.GetVar("solutions"):
514 custom_vars = solution.get("custom_vars", {})
515 solution_deps = self._ParseSolutionDeps(
516 solution["name"],
517 solution_deps_content[solution["name"]],
518 custom_vars)
519
520 # If a line is in custom_deps, but not in the solution, we want to append
521 # this line to the solution.
522 if "custom_deps" in solution:
523 for d in solution["custom_deps"]:
524 if d not in solution_deps:
525 solution_deps[d] = solution["custom_deps"][d]
526
527 for d in solution_deps:
528 if "custom_deps" in solution and d in solution["custom_deps"]:
529 # Dependency is overriden.
530 url = solution["custom_deps"][d]
531 if url is None:
532 continue
533 else:
534 url = solution_deps[d]
535 # if we have a From reference dependent on another solution, then
536 # just skip the From reference. When we pull deps for the solution,
537 # we will take care of this dependency.
538 #
539 # If multiple solutions all have the same From reference, then we
540 # should only add one to our list of dependencies.
541 if type(url) != str:
542 if url.module_name in solution_urls:
543 # Already parsed.
544 continue
545 if d in deps and type(deps[d]) != str:
546 if url.module_name == deps[d].module_name:
547 continue
548 else:
549 parsed_url = urlparse.urlparse(url)
550 scheme = parsed_url[0]
551 if not scheme:
552 # A relative url. Fetch the real base.
553 path = parsed_url[2]
554 if path[0] != "/":
555 raise Error(
556 "relative DEPS entry \"%s\" must begin with a slash" % d)
557 # Create a scm just to query the full url.
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +0000558 scm = gclient_scm.SCMWrapper(solution["url"], self._root_dir,
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000559 None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000560 url = scm.FullUrlForRelativeUrl(url)
561 if d in deps and deps[d] != url:
562 raise Error(
563 "Solutions have conflicting versions of dependency \"%s\"" % d)
564 if d in solution_urls and solution_urls[d] != url:
565 raise Error(
566 "Dependency \"%s\" conflicts with specified solution" % d)
567 # Grab the dependency.
568 deps[d] = url
569 return deps
570
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000571 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000572 """Runs the action from a single hook.
573 """
574 command = hook_dict['action'][:]
575 if command[0] == 'python':
576 # If the hook specified "python" as the first item, the action is a
577 # Python script. Run it by starting a new copy of the same
578 # interpreter.
579 command[0] = sys.executable
580
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000581 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000582 splice_index = command.index('$matching_files')
583 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000584
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000585 # Use a discrete exit status code of 2 to indicate that a hook action
586 # failed. Users of this script may wish to treat hook action failures
587 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000588 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000589
590 def _RunHooks(self, command, file_list, is_using_git):
591 """Evaluates all hooks, running actions as needed.
592 """
593 # Hooks only run for these command types.
594 if not command in ('update', 'revert', 'runhooks'):
595 return
596
evan@chromium.org67820ef2009-07-27 17:23:00 +0000597 # Hooks only run when --nohooks is not specified
598 if self._options.nohooks:
599 return
600
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000601 # Get any hooks from the .gclient file.
602 hooks = self.GetVar("hooks", [])
603 # Add any hooks found in DEPS files.
604 hooks.extend(self._deps_hooks)
605
606 # If "--force" was specified, run all hooks regardless of what files have
607 # changed. If the user is using git, then we don't know what files have
608 # changed so we always run all hooks.
609 if self._options.force or is_using_git:
610 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000611 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000612 return
613
614 # Run hooks on the basis of whether the files from the gclient operation
615 # match each hook's pattern.
616 for hook_dict in hooks:
617 pattern = re.compile(hook_dict['pattern'])
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000618 matching_file_list = [file for file in file_list if pattern.search(file)]
619 if matching_file_list:
620 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000621
622 def RunOnDeps(self, command, args):
623 """Runs a command on each dependency in a client and its dependencies.
624
625 The module's dependencies are specified in its top-level DEPS files.
626
627 Args:
628 command: The command to use (e.g., 'status' or 'diff')
629 args: list of str - extra arguments to add to the command line.
630
631 Raises:
632 Error: If the client has conflicting entries.
633 """
634 if not command in self.supported_commands:
635 raise Error("'%s' is an unsupported command" % command)
636
637 # Check for revision overrides.
638 revision_overrides = {}
639 for revision in self._options.revisions:
640 if revision.find("@") == -1:
641 raise Error(
642 "Specify the full dependency when specifying a revision number.")
643 revision_elem = revision.split("@")
644 # Disallow conflicting revs
645 if revision_overrides.has_key(revision_elem[0]) and \
646 revision_overrides[revision_elem[0]] != revision_elem[1]:
647 raise Error(
648 "Conflicting revision numbers specified.")
649 revision_overrides[revision_elem[0]] = revision_elem[1]
650
651 solutions = self.GetVar("solutions")
652 if not solutions:
653 raise Error("No solution specified")
654
655 # When running runhooks --force, there's no need to consult the SCM.
656 # All known hooks are expected to run unconditionally regardless of working
657 # copy state, so skip the SCM status check.
658 run_scm = not (command == 'runhooks' and self._options.force)
659
660 entries = {}
661 entries_deps_content = {}
662 file_list = []
663 # Run on the base solutions first.
664 for solution in solutions:
665 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000666 deps_file = solution.get("deps_file", self._options.deps_file)
667 if '/' in deps_file or '\\' in deps_file:
668 raise Error("deps_file name must not be a path, just a filename.")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000669 if name in entries:
670 raise Error("solution %s specified more than once" % name)
671 url = solution["url"]
672 entries[name] = url
673 if run_scm:
674 self._options.revision = revision_overrides.get(name)
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +0000675 scm = gclient_scm.SCMWrapper(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000676 scm.RunCommand(command, self._options, args, file_list)
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000677 file_list = [os.path.join(name, file.strip()) for file in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000678 self._options.revision = None
679 try:
680 deps_content = FileRead(os.path.join(self._root_dir, name,
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000681 deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000682 except IOError, e:
683 if e.errno != errno.ENOENT:
684 raise
685 deps_content = ""
686 entries_deps_content[name] = deps_content
687
688 # Process the dependencies next (sort alphanumerically to ensure that
689 # containing directories get populated first and for readability)
690 deps = self._ParseAllDeps(entries, entries_deps_content)
691 deps_to_process = deps.keys()
692 deps_to_process.sort()
693
694 # First pass for direct dependencies.
695 for d in deps_to_process:
696 if type(deps[d]) == str:
697 url = deps[d]
698 entries[d] = url
699 if run_scm:
700 self._options.revision = revision_overrides.get(d)
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +0000701 scm = gclient_scm.SCMWrapper(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000702 scm.RunCommand(command, self._options, args, file_list)
703 self._options.revision = None
704
705 # Second pass for inherited deps (via the From keyword)
706 for d in deps_to_process:
707 if type(deps[d]) != str:
708 sub_deps = self._ParseSolutionDeps(
709 deps[d].module_name,
710 FileRead(os.path.join(self._root_dir,
711 deps[d].module_name,
712 self._options.deps_file)),
713 {})
714 url = sub_deps[d]
715 entries[d] = url
716 if run_scm:
717 self._options.revision = revision_overrides.get(d)
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +0000718 scm = gclient_scm.SCMWrapper(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000719 scm.RunCommand(command, self._options, args, file_list)
720 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000721
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000722 # Convert all absolute paths to relative.
723 for i in range(len(file_list)):
724 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
725 # It depends on the command being executed (like runhooks vs sync).
726 if not os.path.isabs(file_list[i]):
727 continue
728
729 prefix = os.path.commonprefix([self._root_dir.lower(),
730 file_list[i].lower()])
731 file_list[i] = file_list[i][len(prefix):]
732
733 # Strip any leading path separators.
734 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
735 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000736
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000737 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000738 self._RunHooks(command, file_list, is_using_git)
739
740 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000741 # Notify the user if there is an orphaned entry in their working copy.
742 # Only delete the directory if there are no changes in it, and
743 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000744 prev_entries = self._ReadEntries()
745 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000746 # Fix path separator on Windows.
747 entry_fixed = entry.replace('/', os.path.sep)
748 e_dir = os.path.join(self._root_dir, entry_fixed)
749 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000750 if entry not in entries and os.path.exists(e_dir):
ajwong@chromium.org8399dc02009-06-23 21:36:25 +0000751 if not self._options.delete_unversioned_trees or \
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000752 gclient_scm.CaptureSVNStatus(e_dir):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000753 # There are modified files in this entry. Keep warning until
754 # removed.
755 entries[entry] = None
756 print(("\nWARNING: \"%s\" is no longer part of this client. "
757 "It is recommended that you manually remove it.\n") %
758 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000759 else:
760 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000761 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000762 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000763 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000764 # record the current list of entries for next time
765 self._SaveEntries(entries)
766
767 def PrintRevInfo(self):
768 """Output revision info mapping for the client and its dependencies. This
769 allows the capture of a overall "revision" for the source tree that can
770 be used to reproduce the same tree in the future. The actual output
771 contains enough information (source paths, svn server urls and revisions)
772 that it can be used either to generate external svn commands (without
773 gclient) or as input to gclient's --rev option (with some massaging of
774 the data).
775
776 NOTE: Unlike RunOnDeps this does not require a local checkout and is run
777 on the Pulse master. It MUST NOT execute hooks.
778
779 Raises:
780 Error: If the client has conflicting entries.
781 """
782 # Check for revision overrides.
783 revision_overrides = {}
784 for revision in self._options.revisions:
785 if revision.find("@") < 0:
786 raise Error(
787 "Specify the full dependency when specifying a revision number.")
788 revision_elem = revision.split("@")
789 # Disallow conflicting revs
790 if revision_overrides.has_key(revision_elem[0]) and \
791 revision_overrides[revision_elem[0]] != revision_elem[1]:
792 raise Error(
793 "Conflicting revision numbers specified.")
794 revision_overrides[revision_elem[0]] = revision_elem[1]
795
796 solutions = self.GetVar("solutions")
797 if not solutions:
798 raise Error("No solution specified")
799
800 entries = {}
801 entries_deps_content = {}
802
803 # Inner helper to generate base url and rev tuple (including honoring
804 # |revision_overrides|)
805 def GetURLAndRev(name, original_url):
806 if original_url.find("@") < 0:
807 if revision_overrides.has_key(name):
808 return (original_url, int(revision_overrides[name]))
809 else:
810 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000811 return (original_url,
812 gclient_scm.CaptureSVNHeadRevision(original_url))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000813 else:
814 url_components = original_url.split("@")
815 if revision_overrides.has_key(name):
816 return (url_components[0], int(revision_overrides[name]))
817 else:
818 return (url_components[0], int(url_components[1]))
819
820 # Run on the base solutions first.
821 for solution in solutions:
822 name = solution["name"]
823 if name in entries:
824 raise Error("solution %s specified more than once" % name)
825 (url, rev) = GetURLAndRev(name, solution["url"])
826 entries[name] = "%s@%d" % (url, rev)
827 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000828 entries_deps_content[name] = gclient_scm.CaptureSVN(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000829 ["cat",
830 "%s/%s@%d" % (url,
831 self._options.deps_file,
832 rev)],
833 os.getcwd())
834
835 # Process the dependencies next (sort alphanumerically to ensure that
836 # containing directories get populated first and for readability)
837 deps = self._ParseAllDeps(entries, entries_deps_content)
838 deps_to_process = deps.keys()
839 deps_to_process.sort()
840
841 # First pass for direct dependencies.
842 for d in deps_to_process:
843 if type(deps[d]) == str:
844 (url, rev) = GetURLAndRev(d, deps[d])
845 entries[d] = "%s@%d" % (url, rev)
846
847 # Second pass for inherited deps (via the From keyword)
848 for d in deps_to_process:
849 if type(deps[d]) != str:
850 deps_parent_url = entries[deps[d].module_name]
851 if deps_parent_url.find("@") < 0:
852 raise Error("From %s missing revisioned url" % deps[d].module_name)
853 deps_parent_url_components = deps_parent_url.split("@")
854 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000855 deps_parent_content = gclient_scm.CaptureSVN(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000856 ["cat",
857 "%s/%s@%s" % (deps_parent_url_components[0],
858 self._options.deps_file,
859 deps_parent_url_components[1])],
860 os.getcwd())
861 sub_deps = self._ParseSolutionDeps(
862 deps[d].module_name,
863 FileRead(os.path.join(self._root_dir,
864 deps[d].module_name,
865 self._options.deps_file)),
866 {})
867 (url, rev) = GetURLAndRev(d, sub_deps[d])
868 entries[d] = "%s@%d" % (url, rev)
maruel@chromium.org57e893e2009-08-19 18:12:09 +0000869 print(";\n\n".join(["%s: %s" % (x, entries[x])
870 for x in sorted(entries.keys())]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000871
872
873## gclient commands.
874
875
876def DoCleanup(options, args):
877 """Handle the cleanup subcommand.
878
879 Raises:
880 Error: if client isn't configured properly.
881 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000882 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000883 if not client:
884 raise Error("client not configured; see 'gclient config'")
885 if options.verbose:
886 # Print out the .gclient file. This is longer than if we just printed the
887 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000888 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000889 options.verbose = True
890 return client.RunOnDeps('cleanup', args)
891
892
893def DoConfig(options, args):
894 """Handle the config subcommand.
895
896 Args:
897 options: If options.spec set, a string providing contents of config file.
898 args: The command line args. If spec is not set,
899 then args[0] is a string URL to get for config file.
900
901 Raises:
902 Error: on usage error
903 """
904 if len(args) < 1 and not options.spec:
905 raise Error("required argument missing; see 'gclient help config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000906 if os.path.exists(options.config_filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000907 raise Error("%s file already exists in the current directory" %
908 options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000909 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000910 if options.spec:
911 client.SetConfig(options.spec)
912 else:
913 # TODO(darin): it would be nice to be able to specify an alternate relpath
914 # for the given URL.
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000915 base_url = args[0].rstrip('/')
916 name = base_url.split("/")[-1]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000917 safesync_url = ""
918 if len(args) > 1:
919 safesync_url = args[1]
920 client.SetDefaultConfig(name, base_url, safesync_url)
921 client.SaveConfig()
922
923
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000924def DoExport(options, args):
925 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000926
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000927 Raises:
928 Error: on usage error
929 """
930 if len(args) != 1:
931 raise Error("Need directory name")
932 client = GClient.LoadCurrentConfig(options)
933
934 if not client:
935 raise Error("client not configured; see 'gclient config'")
936
937 if options.verbose:
938 # Print out the .gclient file. This is longer than if we just printed the
939 # client dict, but more legible, and it might contain helpful comments.
940 print(client.ConfigContent())
941 return client.RunOnDeps('export', args)
942
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000943def DoHelp(options, args):
944 """Handle the help subcommand giving help for another subcommand.
945
946 Raises:
947 Error: if the command is unknown.
948 """
949 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000950 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000951 else:
952 raise Error("unknown subcommand '%s'; see 'gclient help'" % args[0])
953
954
kbr@google.comab318592009-09-04 00:54:55 +0000955def DoPack(options, args):
956 """Handle the pack subcommand.
957
958 Raises:
959 Error: if client isn't configured properly.
960 """
961 client = GClient.LoadCurrentConfig(options)
962 if not client:
963 raise Error("client not configured; see 'gclient config'")
964 if options.verbose:
965 # Print out the .gclient file. This is longer than if we just printed the
966 # client dict, but more legible, and it might contain helpful comments.
967 print(client.ConfigContent())
968 options.verbose = True
969 return client.RunOnDeps('pack', args)
970
971
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000972def DoStatus(options, args):
973 """Handle the status subcommand.
974
975 Raises:
976 Error: if client isn't configured properly.
977 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000978 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000979 if not client:
980 raise Error("client not configured; see 'gclient config'")
981 if options.verbose:
982 # Print out the .gclient file. This is longer than if we just printed the
983 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000984 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000985 options.verbose = True
986 return client.RunOnDeps('status', args)
987
988
989def DoUpdate(options, args):
990 """Handle the update and sync subcommands.
991
992 Raises:
993 Error: if client isn't configured properly.
994 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000995 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000996
997 if not client:
998 raise Error("client not configured; see 'gclient config'")
999
1000 if not options.head:
1001 solutions = client.GetVar('solutions')
1002 if solutions:
1003 for s in solutions:
1004 if s.get('safesync_url', ''):
1005 # rip through revisions and make sure we're not over-riding
1006 # something that was explicitly passed
1007 has_key = False
1008 for r in options.revisions:
1009 if r.split('@')[0] == s['name']:
1010 has_key = True
1011 break
1012
1013 if not has_key:
1014 handle = urllib.urlopen(s['safesync_url'])
1015 rev = handle.read().strip()
1016 handle.close()
1017 if len(rev):
1018 options.revisions.append(s['name']+'@'+rev)
1019
1020 if options.verbose:
1021 # Print out the .gclient file. This is longer than if we just printed the
1022 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001023 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001024 return client.RunOnDeps('update', args)
1025
1026
1027def DoDiff(options, args):
1028 """Handle the diff subcommand.
1029
1030 Raises:
1031 Error: if client isn't configured properly.
1032 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001033 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001034 if not client:
1035 raise Error("client not configured; see 'gclient config'")
1036 if options.verbose:
1037 # Print out the .gclient file. This is longer than if we just printed the
1038 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001039 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040 options.verbose = True
1041 return client.RunOnDeps('diff', args)
1042
1043
1044def DoRevert(options, args):
1045 """Handle the revert subcommand.
1046
1047 Raises:
1048 Error: if client isn't configured properly.
1049 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001050 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001051 if not client:
1052 raise Error("client not configured; see 'gclient config'")
1053 return client.RunOnDeps('revert', args)
1054
1055
1056def DoRunHooks(options, args):
1057 """Handle the runhooks subcommand.
1058
1059 Raises:
1060 Error: if client isn't configured properly.
1061 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001062 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001063 if not client:
1064 raise Error("client not configured; see 'gclient config'")
1065 if options.verbose:
1066 # Print out the .gclient file. This is longer than if we just printed the
1067 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001068 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001069 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001070 return client.RunOnDeps('runhooks', args)
1071
1072
1073def DoRevInfo(options, args):
1074 """Handle the revinfo subcommand.
1075
1076 Raises:
1077 Error: if client isn't configured properly.
1078 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001079 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001080 if not client:
1081 raise Error("client not configured; see 'gclient config'")
1082 client.PrintRevInfo()
1083
1084
1085gclient_command_map = {
1086 "cleanup": DoCleanup,
1087 "config": DoConfig,
1088 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001089 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001090 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001091 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001092 "status": DoStatus,
1093 "sync": DoUpdate,
1094 "update": DoUpdate,
1095 "revert": DoRevert,
1096 "runhooks": DoRunHooks,
1097 "revinfo" : DoRevInfo,
1098}
1099
1100
1101def DispatchCommand(command, options, args, command_map=None):
1102 """Dispatches the appropriate subcommand based on command line arguments."""
1103 if command_map is None:
1104 command_map = gclient_command_map
1105
1106 if command in command_map:
1107 return command_map[command](options, args)
1108 else:
1109 raise Error("unknown subcommand '%s'; see 'gclient help'" % command)
1110
1111
1112def Main(argv):
1113 """Parse command line arguments and dispatch command."""
1114
1115 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1116 version=__version__)
1117 option_parser.disable_interspersed_args()
1118 option_parser.add_option("", "--force", action="store_true", default=False,
1119 help=("(update/sync only) force update even "
1120 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001121 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1122 help=("(update/sync/revert only) prevent the hooks from "
1123 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001124 option_parser.add_option("", "--revision", action="append", dest="revisions",
1125 metavar="REV", default=[],
1126 help=("(update/sync only) sync to a specific "
1127 "revision, can be used multiple times for "
1128 "each solution, e.g. --revision=src@123, "
1129 "--revision=internal@32"))
1130 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1131 metavar="OS_LIST",
1132 help=("(update/sync only) sync deps for the "
1133 "specified (comma-separated) platform(s); "
1134 "'all' will sync all platforms"))
1135 option_parser.add_option("", "--spec", default=None,
1136 help=("(config only) create a gclient file "
1137 "containing the provided string"))
1138 option_parser.add_option("", "--verbose", action="store_true", default=False,
1139 help="produce additional output for diagnostics")
1140 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1141 default=False,
1142 help="Skip svn up whenever possible by requesting "
1143 "actual HEAD revision from the repository")
1144 option_parser.add_option("", "--head", action="store_true", default=False,
1145 help=("skips any safesync_urls specified in "
1146 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001147 option_parser.add_option("", "--delete_unversioned_trees",
1148 action="store_true", default=False,
1149 help=("on update, delete any unexpected "
1150 "unversioned trees that are in the checkout"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001151
1152 if len(argv) < 2:
1153 # Users don't need to be told to use the 'help' command.
1154 option_parser.print_help()
1155 return 1
1156 # Add manual support for --version as first argument.
1157 if argv[1] == '--version':
1158 option_parser.print_version()
1159 return 0
1160
1161 # Add manual support for --help as first argument.
1162 if argv[1] == '--help':
1163 argv[1] = 'help'
1164
1165 command = argv[1]
1166 options, args = option_parser.parse_args(argv[2:])
1167
1168 if len(argv) < 3 and command == "help":
1169 option_parser.print_help()
1170 return 0
1171
maruel@chromium.org754960e2009-09-21 12:31:05 +00001172 if options.verbose:
1173 logging.basicConfig(level=logging.DEBUG)
1174
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001175 # Files used for configuration and state saving.
1176 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
1177 options.entries_filename = ".gclient_entries"
1178 options.deps_file = "DEPS"
1179
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001180 options.platform = sys.platform
1181 return DispatchCommand(command, options, args)
1182
1183
1184if "__main__" == __name__:
1185 try:
1186 result = Main(sys.argv)
1187 except Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001188 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001189 result = 1
1190 sys.exit(result)
1191
1192# vim: ts=2:sw=2:tw=80:et: