blob: e56a56ac9f3722a2b11f165f112e3c0a882855a8 [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
72import optparse
73import os
74import re
75import stat
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076import sys
77import time
78import 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.org5f3eee32009-09-17 00:34:30 +0000558 scm = gclient_scm.SCMWrapper(solution["url"], self._root_dir,
559 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.org5f3eee32009-09-17 00:34:30 +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.org5f3eee32009-09-17 00:34:30 +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.org5f3eee32009-09-17 00:34:30 +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 \
752 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.orgdf7a3132009-05-12 17:49:49 +0000811 return (original_url, CaptureSVNHeadRevision(original_url))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000812 else:
813 url_components = original_url.split("@")
814 if revision_overrides.has_key(name):
815 return (url_components[0], int(revision_overrides[name]))
816 else:
817 return (url_components[0], int(url_components[1]))
818
819 # Run on the base solutions first.
820 for solution in solutions:
821 name = solution["name"]
822 if name in entries:
823 raise Error("solution %s specified more than once" % name)
824 (url, rev) = GetURLAndRev(name, solution["url"])
825 entries[name] = "%s@%d" % (url, rev)
826 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
827 entries_deps_content[name] = CaptureSVN(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000828 ["cat",
829 "%s/%s@%d" % (url,
830 self._options.deps_file,
831 rev)],
832 os.getcwd())
833
834 # Process the dependencies next (sort alphanumerically to ensure that
835 # containing directories get populated first and for readability)
836 deps = self._ParseAllDeps(entries, entries_deps_content)
837 deps_to_process = deps.keys()
838 deps_to_process.sort()
839
840 # First pass for direct dependencies.
841 for d in deps_to_process:
842 if type(deps[d]) == str:
843 (url, rev) = GetURLAndRev(d, deps[d])
844 entries[d] = "%s@%d" % (url, rev)
845
846 # Second pass for inherited deps (via the From keyword)
847 for d in deps_to_process:
848 if type(deps[d]) != str:
849 deps_parent_url = entries[deps[d].module_name]
850 if deps_parent_url.find("@") < 0:
851 raise Error("From %s missing revisioned url" % deps[d].module_name)
852 deps_parent_url_components = deps_parent_url.split("@")
853 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
854 deps_parent_content = CaptureSVN(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000855 ["cat",
856 "%s/%s@%s" % (deps_parent_url_components[0],
857 self._options.deps_file,
858 deps_parent_url_components[1])],
859 os.getcwd())
860 sub_deps = self._ParseSolutionDeps(
861 deps[d].module_name,
862 FileRead(os.path.join(self._root_dir,
863 deps[d].module_name,
864 self._options.deps_file)),
865 {})
866 (url, rev) = GetURLAndRev(d, sub_deps[d])
867 entries[d] = "%s@%d" % (url, rev)
maruel@chromium.org57e893e2009-08-19 18:12:09 +0000868 print(";\n\n".join(["%s: %s" % (x, entries[x])
869 for x in sorted(entries.keys())]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000870
871
872## gclient commands.
873
874
875def DoCleanup(options, args):
876 """Handle the cleanup subcommand.
877
878 Raises:
879 Error: if client isn't configured properly.
880 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000881 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000882 if not client:
883 raise Error("client not configured; see 'gclient config'")
884 if options.verbose:
885 # Print out the .gclient file. This is longer than if we just printed the
886 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000887 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000888 options.verbose = True
889 return client.RunOnDeps('cleanup', args)
890
891
892def DoConfig(options, args):
893 """Handle the config subcommand.
894
895 Args:
896 options: If options.spec set, a string providing contents of config file.
897 args: The command line args. If spec is not set,
898 then args[0] is a string URL to get for config file.
899
900 Raises:
901 Error: on usage error
902 """
903 if len(args) < 1 and not options.spec:
904 raise Error("required argument missing; see 'gclient help config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000905 if os.path.exists(options.config_filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000906 raise Error("%s file already exists in the current directory" %
907 options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000908 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000909 if options.spec:
910 client.SetConfig(options.spec)
911 else:
912 # TODO(darin): it would be nice to be able to specify an alternate relpath
913 # for the given URL.
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000914 base_url = args[0].rstrip('/')
915 name = base_url.split("/")[-1]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000916 safesync_url = ""
917 if len(args) > 1:
918 safesync_url = args[1]
919 client.SetDefaultConfig(name, base_url, safesync_url)
920 client.SaveConfig()
921
922
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000923def DoExport(options, args):
924 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000925
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000926 Raises:
927 Error: on usage error
928 """
929 if len(args) != 1:
930 raise Error("Need directory name")
931 client = GClient.LoadCurrentConfig(options)
932
933 if not client:
934 raise Error("client not configured; see 'gclient config'")
935
936 if options.verbose:
937 # Print out the .gclient file. This is longer than if we just printed the
938 # client dict, but more legible, and it might contain helpful comments.
939 print(client.ConfigContent())
940 return client.RunOnDeps('export', args)
941
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000942def DoHelp(options, args):
943 """Handle the help subcommand giving help for another subcommand.
944
945 Raises:
946 Error: if the command is unknown.
947 """
948 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000949 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000950 else:
951 raise Error("unknown subcommand '%s'; see 'gclient help'" % args[0])
952
953
kbr@google.comab318592009-09-04 00:54:55 +0000954def DoPack(options, args):
955 """Handle the pack subcommand.
956
957 Raises:
958 Error: if client isn't configured properly.
959 """
960 client = GClient.LoadCurrentConfig(options)
961 if not client:
962 raise Error("client not configured; see 'gclient config'")
963 if options.verbose:
964 # Print out the .gclient file. This is longer than if we just printed the
965 # client dict, but more legible, and it might contain helpful comments.
966 print(client.ConfigContent())
967 options.verbose = True
968 return client.RunOnDeps('pack', args)
969
970
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000971def DoStatus(options, args):
972 """Handle the status subcommand.
973
974 Raises:
975 Error: if client isn't configured properly.
976 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000977 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000978 if not client:
979 raise Error("client not configured; see 'gclient config'")
980 if options.verbose:
981 # Print out the .gclient file. This is longer than if we just printed the
982 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000983 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000984 options.verbose = True
985 return client.RunOnDeps('status', args)
986
987
988def DoUpdate(options, args):
989 """Handle the update and sync subcommands.
990
991 Raises:
992 Error: if client isn't configured properly.
993 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000994 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000995
996 if not client:
997 raise Error("client not configured; see 'gclient config'")
998
999 if not options.head:
1000 solutions = client.GetVar('solutions')
1001 if solutions:
1002 for s in solutions:
1003 if s.get('safesync_url', ''):
1004 # rip through revisions and make sure we're not over-riding
1005 # something that was explicitly passed
1006 has_key = False
1007 for r in options.revisions:
1008 if r.split('@')[0] == s['name']:
1009 has_key = True
1010 break
1011
1012 if not has_key:
1013 handle = urllib.urlopen(s['safesync_url'])
1014 rev = handle.read().strip()
1015 handle.close()
1016 if len(rev):
1017 options.revisions.append(s['name']+'@'+rev)
1018
1019 if options.verbose:
1020 # Print out the .gclient file. This is longer than if we just printed the
1021 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001022 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001023 return client.RunOnDeps('update', args)
1024
1025
1026def DoDiff(options, args):
1027 """Handle the diff subcommand.
1028
1029 Raises:
1030 Error: if client isn't configured properly.
1031 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001032 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001033 if not client:
1034 raise Error("client not configured; see 'gclient config'")
1035 if options.verbose:
1036 # Print out the .gclient file. This is longer than if we just printed the
1037 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001038 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001039 options.verbose = True
1040 return client.RunOnDeps('diff', args)
1041
1042
1043def DoRevert(options, args):
1044 """Handle the revert subcommand.
1045
1046 Raises:
1047 Error: if client isn't configured properly.
1048 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001049 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001050 if not client:
1051 raise Error("client not configured; see 'gclient config'")
1052 return client.RunOnDeps('revert', args)
1053
1054
1055def DoRunHooks(options, args):
1056 """Handle the runhooks subcommand.
1057
1058 Raises:
1059 Error: if client isn't configured properly.
1060 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001061 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001062 if not client:
1063 raise Error("client not configured; see 'gclient config'")
1064 if options.verbose:
1065 # Print out the .gclient file. This is longer than if we just printed the
1066 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001067 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001068 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001069 return client.RunOnDeps('runhooks', args)
1070
1071
1072def DoRevInfo(options, args):
1073 """Handle the revinfo subcommand.
1074
1075 Raises:
1076 Error: if client isn't configured properly.
1077 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001078 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001079 if not client:
1080 raise Error("client not configured; see 'gclient config'")
1081 client.PrintRevInfo()
1082
1083
1084gclient_command_map = {
1085 "cleanup": DoCleanup,
1086 "config": DoConfig,
1087 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001088 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001089 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001090 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001091 "status": DoStatus,
1092 "sync": DoUpdate,
1093 "update": DoUpdate,
1094 "revert": DoRevert,
1095 "runhooks": DoRunHooks,
1096 "revinfo" : DoRevInfo,
1097}
1098
1099
1100def DispatchCommand(command, options, args, command_map=None):
1101 """Dispatches the appropriate subcommand based on command line arguments."""
1102 if command_map is None:
1103 command_map = gclient_command_map
1104
1105 if command in command_map:
1106 return command_map[command](options, args)
1107 else:
1108 raise Error("unknown subcommand '%s'; see 'gclient help'" % command)
1109
1110
1111def Main(argv):
1112 """Parse command line arguments and dispatch command."""
1113
1114 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1115 version=__version__)
1116 option_parser.disable_interspersed_args()
1117 option_parser.add_option("", "--force", action="store_true", default=False,
1118 help=("(update/sync only) force update even "
1119 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001120 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1121 help=("(update/sync/revert only) prevent the hooks from "
1122 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001123 option_parser.add_option("", "--revision", action="append", dest="revisions",
1124 metavar="REV", default=[],
1125 help=("(update/sync only) sync to a specific "
1126 "revision, can be used multiple times for "
1127 "each solution, e.g. --revision=src@123, "
1128 "--revision=internal@32"))
1129 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1130 metavar="OS_LIST",
1131 help=("(update/sync only) sync deps for the "
1132 "specified (comma-separated) platform(s); "
1133 "'all' will sync all platforms"))
1134 option_parser.add_option("", "--spec", default=None,
1135 help=("(config only) create a gclient file "
1136 "containing the provided string"))
1137 option_parser.add_option("", "--verbose", action="store_true", default=False,
1138 help="produce additional output for diagnostics")
1139 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1140 default=False,
1141 help="Skip svn up whenever possible by requesting "
1142 "actual HEAD revision from the repository")
1143 option_parser.add_option("", "--head", action="store_true", default=False,
1144 help=("skips any safesync_urls specified in "
1145 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001146 option_parser.add_option("", "--delete_unversioned_trees",
1147 action="store_true", default=False,
1148 help=("on update, delete any unexpected "
1149 "unversioned trees that are in the checkout"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001150
1151 if len(argv) < 2:
1152 # Users don't need to be told to use the 'help' command.
1153 option_parser.print_help()
1154 return 1
1155 # Add manual support for --version as first argument.
1156 if argv[1] == '--version':
1157 option_parser.print_version()
1158 return 0
1159
1160 # Add manual support for --help as first argument.
1161 if argv[1] == '--help':
1162 argv[1] = 'help'
1163
1164 command = argv[1]
1165 options, args = option_parser.parse_args(argv[2:])
1166
1167 if len(argv) < 3 and command == "help":
1168 option_parser.print_help()
1169 return 0
1170
1171 # Files used for configuration and state saving.
1172 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
1173 options.entries_filename = ".gclient_entries"
1174 options.deps_file = "DEPS"
1175
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001176 options.platform = sys.platform
1177 return DispatchCommand(command, options, args)
1178
1179
1180if "__main__" == __name__:
1181 try:
1182 result = Main(sys.argv)
1183 except Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001184 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001185 result = 1
1186 sys.exit(result)
1187
1188# vim: ts=2:sw=2:tw=80:et: