blob: 386b1a916e77491fc8d50d50d29399934c6833ff [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
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000077import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078import urllib
79
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000080import gclient_scm
81import gclient_utils
82from gclient_utils import Error, FileRead, FileWrite
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000083
84# default help text
85DEFAULT_USAGE_TEXT = (
86"""usage: %prog <subcommand> [options] [--] [svn options/args...]
87a wrapper for managing a set of client modules in svn.
88Version """ + __version__ + """
89
90subcommands:
91 cleanup
92 config
93 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000094 export
kbr@google.comab318592009-09-04 00:54:55 +000095 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000096 revert
97 status
98 sync
99 update
100 runhooks
101 revinfo
102
103Options and extra arguments can be passed to invoked svn commands by
104appending them to the command line. Note that if the first such
105appended option starts with a dash (-) then the options must be
106preceded by -- to distinguish them from gclient options.
107
108For additional help on a subcommand or examples of usage, try
109 %prog help <subcommand>
110 %prog help files
111""")
112
113GENERIC_UPDATE_USAGE_TEXT = (
114 """Perform a checkout/update of the modules specified by the gclient
115configuration; see 'help config'. Unless --revision is specified,
116then the latest revision of the root solutions is checked out, with
117dependent submodule versions updated according to DEPS files.
118If --revision is specified, then the given revision is used in place
119of the latest, either for a single solution or for all solutions.
120Unless the --force option is provided, solutions and modules whose
121local revision matches the one to update (i.e., they have not changed
evan@chromium.org67820ef2009-07-27 17:23:00 +0000122in the repository) are *not* modified. Unless --nohooks is provided,
123the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000124This a synonym for 'gclient %(alias)s'
125
126usage: gclient %(cmd)s [options] [--] [svn update options/args]
127
128Valid options:
129 --force : force update even for unchanged modules
evan@chromium.org67820ef2009-07-27 17:23:00 +0000130 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000131 --revision REV : update/checkout all solutions with specified revision
132 --revision SOLUTION@REV : update given solution to specified revision
133 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
134 --verbose : output additional diagnostics
maruel@chromium.orgb8b6b872009-06-30 18:50:56 +0000135 --head : update to latest revision, instead of last good revision
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000136
137Examples:
138 gclient %(cmd)s
139 update files from SVN according to current configuration,
140 *for modules which have changed since last update or sync*
141 gclient %(cmd)s --force
142 update files from SVN according to current configuration, for
143 all modules (useful for recovering files deleted from local copy)
144""")
145
146COMMAND_USAGE_TEXT = {
147 "cleanup":
148 """Clean up all working copies, using 'svn cleanup' for each module.
149Additional options and args may be passed to 'svn cleanup'.
150
151usage: cleanup [options] [--] [svn cleanup args/options]
152
153Valid options:
154 --verbose : output additional diagnostics
155""",
156 "config": """Create a .gclient file in the current directory; this
157specifies the configuration for further commands. After update/sync,
158top-level DEPS files in each module are read to determine dependent
159modules to operate on as well. If optional [url] parameter is
160provided, then configuration is read from a specified Subversion server
161URL. Otherwise, a --spec option must be provided.
162
163usage: config [option | url] [safesync url]
164
165Valid options:
166 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
167 *Note that due to Cygwin/Python brokenness, it
168 probably can't contain any newlines.*
169
170Examples:
171 gclient config https://gclient.googlecode.com/svn/trunk/gclient
172 configure a new client to check out gclient.py tool sources
173 gclient config --spec='solutions=[{"name":"gclient","""
174 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
175 '"custom_deps":{}}]',
176 "diff": """Display the differences between two revisions of modules.
177(Does 'svn diff' for each checked out module and dependences.)
178Additional args and options to 'svn diff' can be passed after
179gclient options.
180
181usage: diff [options] [--] [svn args/options]
182
183Valid options:
184 --verbose : output additional diagnostics
185
186Examples:
187 gclient diff
188 simple 'svn diff' for configured client and dependences
189 gclient diff -- -x -b
190 use 'svn diff -x -b' to suppress whitespace-only differences
191 gclient diff -- -r HEAD -x -b
192 diff versus the latest version of each module
193""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000194 "export":
195 """Wrapper for svn export for all managed directories
196""",
kbr@google.comab318592009-09-04 00:54:55 +0000197 "pack":
198
199 """Generate a patch which can be applied at the root of the tree.
200Internally, runs 'svn diff' on each checked out module and
201dependencies, and performs minimal postprocessing of the output. The
202resulting patch is printed to stdout and can be applied to a freshly
203checked out tree via 'patch -p0 < patchfile'. Additional args and
204options to 'svn diff' can be passed after gclient options.
205
206usage: pack [options] [--] [svn args/options]
207
208Valid options:
209 --verbose : output additional diagnostics
210
211Examples:
212 gclient pack > patch.txt
213 generate simple patch for configured client and dependences
214 gclient pack -- -x -b > patch.txt
215 generate patch using 'svn diff -x -b' to suppress
216 whitespace-only differences
217 gclient pack -- -r HEAD -x -b > patch.txt
218 generate patch, diffing each file versus the latest version of
219 each module
220""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000221 "revert":
222 """Revert every file in every managed directory in the client view.
223
224usage: revert
225""",
226 "status":
227 """Show the status of client and dependent modules, using 'svn diff'
228for each module. Additional options and args may be passed to 'svn diff'.
229
230usage: status [options] [--] [svn diff args/options]
231
232Valid options:
233 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000234 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000235""",
236 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
237 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
238 "help": """Describe the usage of this program or its subcommands.
239
240usage: help [options] [subcommand]
241
242Valid options:
243 --verbose : output additional diagnostics
244""",
245 "runhooks":
246 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000247according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000248
249usage: runhooks [options]
250
251Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000252 --verbose : output additional diagnostics
253""",
254 "revinfo":
255 """Outputs source path, server URL and revision information for every
256dependency in all solutions (no local checkout required).
257
258usage: revinfo [options]
259""",
260}
261
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000262DEFAULT_CLIENT_FILE_TEXT = ("""\
263# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000264# that will be checked out into your working copy. Each solution may
265# optionally define additional dependencies (via its DEPS file) to be
266# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000267# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000268# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000269# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000270# a text file which contains nothing but the last known good SCM revision to
271# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000272
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000273solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000274 { "name" : "%(solution_name)s",
275 "url" : "%(solution_url)s",
276 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000277 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000278 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000279 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000280 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000281 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000282 "safesync_url": "%(safesync_url)s"
283 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000284]
285""")
286
287
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000288## GClient implementation.
289
290
291class GClient(object):
292 """Object that represent a gclient checkout."""
293
294 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000295 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
296 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000297 ]
298
299 def __init__(self, root_dir, options):
300 self._root_dir = root_dir
301 self._options = options
302 self._config_content = None
303 self._config_dict = {}
304 self._deps_hooks = []
305
306 def SetConfig(self, content):
307 self._config_dict = {}
308 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000309 try:
310 exec(content, self._config_dict)
311 except SyntaxError, e:
312 try:
313 # Try to construct a human readable error message
314 error_message = [
315 'There is a syntax error in your configuration file.',
316 'Line #%s, character %s:' % (e.lineno, e.offset),
317 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
318 except:
319 # Something went wrong, re-raise the original exception
320 raise e
321 else:
322 # Raise a new exception with the human readable message:
323 raise Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000324
325 def SaveConfig(self):
326 FileWrite(os.path.join(self._root_dir, self._options.config_filename),
327 self._config_content)
328
329 def _LoadConfig(self):
330 client_source = FileRead(os.path.join(self._root_dir,
331 self._options.config_filename))
332 self.SetConfig(client_source)
333
334 def ConfigContent(self):
335 return self._config_content
336
337 def GetVar(self, key, default=None):
338 return self._config_dict.get(key, default)
339
340 @staticmethod
341 def LoadCurrentConfig(options, from_dir=None):
342 """Searches for and loads a .gclient file relative to the current working
343 dir.
344
345 Returns:
346 A dict representing the contents of the .gclient file or an empty dict if
347 the .gclient file doesn't exist.
348 """
349 if not from_dir:
350 from_dir = os.curdir
351 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000352 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000353 next = os.path.split(path)
354 if not next[1]:
355 return None
356 path = next[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000357 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000358 client._LoadConfig()
359 return client
360
361 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000362 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
363 'solution_name': solution_name,
364 'solution_url': solution_url,
365 'safesync_url' : safesync_url,
366 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000367
368 def _SaveEntries(self, entries):
369 """Creates a .gclient_entries file to record the list of unique checkouts.
370
371 The .gclient_entries file lives in the same directory as .gclient.
372
373 Args:
374 entries: A sequence of solution names.
375 """
376 text = "entries = [\n"
377 for entry in entries:
378 text += " \"%s\",\n" % entry
379 text += "]\n"
380 FileWrite(os.path.join(self._root_dir, self._options.entries_filename),
381 text)
382
383 def _ReadEntries(self):
384 """Read the .gclient_entries file for the given client.
385
386 Args:
387 client: The client for which the entries file should be read.
388
389 Returns:
390 A sequence of solution names, which will be empty if there is the
391 entries file hasn't been created yet.
392 """
393 scope = {}
394 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000395 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000396 return []
397 exec(FileRead(filename), scope)
398 return scope["entries"]
399
400 class FromImpl:
401 """Used to implement the From syntax."""
402
403 def __init__(self, module_name):
404 self.module_name = module_name
405
406 def __str__(self):
407 return 'From("%s")' % self.module_name
408
409 class _VarImpl:
410 def __init__(self, custom_vars, local_scope):
411 self._custom_vars = custom_vars
412 self._local_scope = local_scope
413
414 def Lookup(self, var_name):
415 """Implements the Var syntax."""
416 if var_name in self._custom_vars:
417 return self._custom_vars[var_name]
418 elif var_name in self._local_scope.get("vars", {}):
419 return self._local_scope["vars"][var_name]
420 raise Error("Var is not defined: %s" % var_name)
421
422 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
423 custom_vars):
424 """Parses the DEPS file for the specified solution.
425
426 Args:
427 solution_name: The name of the solution to query.
428 solution_deps_content: Content of the DEPS file for the solution
429 custom_vars: A dict of vars to override any vars defined in the DEPS file.
430
431 Returns:
432 A dict mapping module names (as relative paths) to URLs or an empty
433 dict if the solution does not have a DEPS file.
434 """
435 # Skip empty
436 if not solution_deps_content:
437 return {}
438 # Eval the content
439 local_scope = {}
440 var = self._VarImpl(custom_vars, local_scope)
441 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
442 exec(solution_deps_content, global_scope, local_scope)
443 deps = local_scope.get("deps", {})
444
445 # load os specific dependencies if defined. these dependencies may
446 # override or extend the values defined by the 'deps' member.
447 if "deps_os" in local_scope:
448 deps_os_choices = {
449 "win32": "win",
450 "win": "win",
451 "cygwin": "win",
452 "darwin": "mac",
453 "mac": "mac",
454 "unix": "unix",
455 "linux": "unix",
456 "linux2": "unix",
457 }
458
459 if self._options.deps_os is not None:
460 deps_to_include = self._options.deps_os.split(",")
461 if "all" in deps_to_include:
462 deps_to_include = deps_os_choices.values()
463 else:
464 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
465
466 deps_to_include = set(deps_to_include)
467 for deps_os_key in deps_to_include:
468 os_deps = local_scope["deps_os"].get(deps_os_key, {})
469 if len(deps_to_include) > 1:
470 # Ignore any overrides when including deps for more than one
471 # platform, so we collect the broadest set of dependencies available.
472 # We may end up with the wrong revision of something for our
473 # platform, but this is the best we can do.
474 deps.update([x for x in os_deps.items() if not x[0] in deps])
475 else:
476 deps.update(os_deps)
477
478 if 'hooks' in local_scope:
479 self._deps_hooks.extend(local_scope['hooks'])
480
481 # If use_relative_paths is set in the DEPS file, regenerate
482 # the dictionary using paths relative to the directory containing
483 # the DEPS file.
484 if local_scope.get('use_relative_paths'):
485 rel_deps = {}
486 for d, url in deps.items():
487 # normpath is required to allow DEPS to use .. in their
488 # dependency local path.
489 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
490 return rel_deps
491 else:
492 return deps
493
494 def _ParseAllDeps(self, solution_urls, solution_deps_content):
495 """Parse the complete list of dependencies for the client.
496
497 Args:
498 solution_urls: A dict mapping module names (as relative paths) to URLs
499 corresponding to the solutions specified by the client. This parameter
500 is passed as an optimization.
501 solution_deps_content: A dict mapping module names to the content
502 of their DEPS files
503
504 Returns:
505 A dict mapping module names (as relative paths) to URLs corresponding
506 to the entire set of dependencies to checkout for the given client.
507
508 Raises:
509 Error: If a dependency conflicts with another dependency or of a solution.
510 """
511 deps = {}
512 for solution in self.GetVar("solutions"):
513 custom_vars = solution.get("custom_vars", {})
514 solution_deps = self._ParseSolutionDeps(
515 solution["name"],
516 solution_deps_content[solution["name"]],
517 custom_vars)
518
519 # If a line is in custom_deps, but not in the solution, we want to append
520 # this line to the solution.
521 if "custom_deps" in solution:
522 for d in solution["custom_deps"]:
523 if d not in solution_deps:
524 solution_deps[d] = solution["custom_deps"][d]
525
526 for d in solution_deps:
527 if "custom_deps" in solution and d in solution["custom_deps"]:
528 # Dependency is overriden.
529 url = solution["custom_deps"][d]
530 if url is None:
531 continue
532 else:
533 url = solution_deps[d]
534 # if we have a From reference dependent on another solution, then
535 # just skip the From reference. When we pull deps for the solution,
536 # we will take care of this dependency.
537 #
538 # If multiple solutions all have the same From reference, then we
539 # should only add one to our list of dependencies.
540 if type(url) != str:
541 if url.module_name in solution_urls:
542 # Already parsed.
543 continue
544 if d in deps and type(deps[d]) != str:
545 if url.module_name == deps[d].module_name:
546 continue
547 else:
548 parsed_url = urlparse.urlparse(url)
549 scheme = parsed_url[0]
550 if not scheme:
551 # A relative url. Fetch the real base.
552 path = parsed_url[2]
553 if path[0] != "/":
554 raise Error(
555 "relative DEPS entry \"%s\" must begin with a slash" % d)
556 # Create a scm just to query the full url.
msb@chromium.org56237082009-09-18 19:30:58 +0000557 scm = gclient_scm.create_scm(solution["url"], self._root_dir,
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000558 None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000559 url = scm.FullUrlForRelativeUrl(url)
560 if d in deps and deps[d] != url:
561 raise Error(
562 "Solutions have conflicting versions of dependency \"%s\"" % d)
563 if d in solution_urls and solution_urls[d] != url:
564 raise Error(
565 "Dependency \"%s\" conflicts with specified solution" % d)
566 # Grab the dependency.
567 deps[d] = url
568 return deps
569
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000570 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000571 """Runs the action from a single hook.
572 """
573 command = hook_dict['action'][:]
574 if command[0] == 'python':
575 # If the hook specified "python" as the first item, the action is a
576 # Python script. Run it by starting a new copy of the same
577 # interpreter.
578 command[0] = sys.executable
579
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000580 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000581 splice_index = command.index('$matching_files')
582 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000583
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000584 # Use a discrete exit status code of 2 to indicate that a hook action
585 # failed. Users of this script may wish to treat hook action failures
586 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000587 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000588
589 def _RunHooks(self, command, file_list, is_using_git):
590 """Evaluates all hooks, running actions as needed.
591 """
592 # Hooks only run for these command types.
593 if not command in ('update', 'revert', 'runhooks'):
594 return
595
evan@chromium.org67820ef2009-07-27 17:23:00 +0000596 # Hooks only run when --nohooks is not specified
597 if self._options.nohooks:
598 return
599
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000600 # Get any hooks from the .gclient file.
601 hooks = self.GetVar("hooks", [])
602 # Add any hooks found in DEPS files.
603 hooks.extend(self._deps_hooks)
604
605 # If "--force" was specified, run all hooks regardless of what files have
606 # changed. If the user is using git, then we don't know what files have
607 # changed so we always run all hooks.
608 if self._options.force or is_using_git:
609 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000610 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000611 return
612
613 # Run hooks on the basis of whether the files from the gclient operation
614 # match each hook's pattern.
615 for hook_dict in hooks:
616 pattern = re.compile(hook_dict['pattern'])
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000617 matching_file_list = [file for file in file_list if pattern.search(file)]
618 if matching_file_list:
619 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000620
621 def RunOnDeps(self, command, args):
622 """Runs a command on each dependency in a client and its dependencies.
623
624 The module's dependencies are specified in its top-level DEPS files.
625
626 Args:
627 command: The command to use (e.g., 'status' or 'diff')
628 args: list of str - extra arguments to add to the command line.
629
630 Raises:
631 Error: If the client has conflicting entries.
632 """
633 if not command in self.supported_commands:
634 raise Error("'%s' is an unsupported command" % command)
635
636 # Check for revision overrides.
637 revision_overrides = {}
638 for revision in self._options.revisions:
639 if revision.find("@") == -1:
640 raise Error(
641 "Specify the full dependency when specifying a revision number.")
642 revision_elem = revision.split("@")
643 # Disallow conflicting revs
644 if revision_overrides.has_key(revision_elem[0]) and \
645 revision_overrides[revision_elem[0]] != revision_elem[1]:
646 raise Error(
647 "Conflicting revision numbers specified.")
648 revision_overrides[revision_elem[0]] = revision_elem[1]
649
650 solutions = self.GetVar("solutions")
651 if not solutions:
652 raise Error("No solution specified")
653
654 # When running runhooks --force, there's no need to consult the SCM.
655 # All known hooks are expected to run unconditionally regardless of working
656 # copy state, so skip the SCM status check.
657 run_scm = not (command == 'runhooks' and self._options.force)
658
659 entries = {}
660 entries_deps_content = {}
661 file_list = []
662 # Run on the base solutions first.
663 for solution in solutions:
664 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000665 deps_file = solution.get("deps_file", self._options.deps_file)
666 if '/' in deps_file or '\\' in deps_file:
667 raise Error("deps_file name must not be a path, just a filename.")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000668 if name in entries:
669 raise Error("solution %s specified more than once" % name)
670 url = solution["url"]
671 entries[name] = url
672 if run_scm:
673 self._options.revision = revision_overrides.get(name)
msb@chromium.org56237082009-09-18 19:30:58 +0000674 scm = gclient_scm.create_scm(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000675 scm.RunCommand(command, self._options, args, file_list)
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000676 file_list = [os.path.join(name, file.strip()) for file in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000677 self._options.revision = None
678 try:
679 deps_content = FileRead(os.path.join(self._root_dir, name,
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000680 deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000681 except IOError, e:
682 if e.errno != errno.ENOENT:
683 raise
684 deps_content = ""
685 entries_deps_content[name] = deps_content
686
687 # Process the dependencies next (sort alphanumerically to ensure that
688 # containing directories get populated first and for readability)
689 deps = self._ParseAllDeps(entries, entries_deps_content)
690 deps_to_process = deps.keys()
691 deps_to_process.sort()
692
693 # First pass for direct dependencies.
694 for d in deps_to_process:
695 if type(deps[d]) == str:
696 url = deps[d]
697 entries[d] = url
698 if run_scm:
699 self._options.revision = revision_overrides.get(d)
msb@chromium.org56237082009-09-18 19:30:58 +0000700 scm = gclient_scm.create_scm(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000701 scm.RunCommand(command, self._options, args, file_list)
702 self._options.revision = None
703
704 # Second pass for inherited deps (via the From keyword)
705 for d in deps_to_process:
706 if type(deps[d]) != str:
707 sub_deps = self._ParseSolutionDeps(
708 deps[d].module_name,
709 FileRead(os.path.join(self._root_dir,
710 deps[d].module_name,
711 self._options.deps_file)),
712 {})
713 url = sub_deps[d]
714 entries[d] = url
715 if run_scm:
716 self._options.revision = revision_overrides.get(d)
msb@chromium.org56237082009-09-18 19:30:58 +0000717 scm = gclient_scm.create_scm(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000718 scm.RunCommand(command, self._options, args, file_list)
719 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000720
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000721 # Convert all absolute paths to relative.
722 for i in range(len(file_list)):
723 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
724 # It depends on the command being executed (like runhooks vs sync).
725 if not os.path.isabs(file_list[i]):
726 continue
727
728 prefix = os.path.commonprefix([self._root_dir.lower(),
729 file_list[i].lower()])
730 file_list[i] = file_list[i][len(prefix):]
731
732 # Strip any leading path separators.
733 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
734 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000735
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000736 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000737 self._RunHooks(command, file_list, is_using_git)
738
739 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000740 # Notify the user if there is an orphaned entry in their working copy.
741 # Only delete the directory if there are no changes in it, and
742 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000743 prev_entries = self._ReadEntries()
744 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000745 # Fix path separator on Windows.
746 entry_fixed = entry.replace('/', os.path.sep)
747 e_dir = os.path.join(self._root_dir, entry_fixed)
748 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000749 if entry not in entries and os.path.exists(e_dir):
ajwong@chromium.org8399dc02009-06-23 21:36:25 +0000750 if not self._options.delete_unversioned_trees or \
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000751 gclient_scm.CaptureSVNStatus(e_dir):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000752 # There are modified files in this entry. Keep warning until
753 # removed.
754 entries[entry] = None
755 print(("\nWARNING: \"%s\" is no longer part of this client. "
756 "It is recommended that you manually remove it.\n") %
757 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000758 else:
759 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000760 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000761 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000762 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000763 # record the current list of entries for next time
764 self._SaveEntries(entries)
765
766 def PrintRevInfo(self):
767 """Output revision info mapping for the client and its dependencies. This
768 allows the capture of a overall "revision" for the source tree that can
769 be used to reproduce the same tree in the future. The actual output
770 contains enough information (source paths, svn server urls and revisions)
771 that it can be used either to generate external svn commands (without
772 gclient) or as input to gclient's --rev option (with some massaging of
773 the data).
774
775 NOTE: Unlike RunOnDeps this does not require a local checkout and is run
776 on the Pulse master. It MUST NOT execute hooks.
777
778 Raises:
779 Error: If the client has conflicting entries.
780 """
781 # Check for revision overrides.
782 revision_overrides = {}
783 for revision in self._options.revisions:
784 if revision.find("@") < 0:
785 raise Error(
786 "Specify the full dependency when specifying a revision number.")
787 revision_elem = revision.split("@")
788 # Disallow conflicting revs
789 if revision_overrides.has_key(revision_elem[0]) and \
790 revision_overrides[revision_elem[0]] != revision_elem[1]:
791 raise Error(
792 "Conflicting revision numbers specified.")
793 revision_overrides[revision_elem[0]] = revision_elem[1]
794
795 solutions = self.GetVar("solutions")
796 if not solutions:
797 raise Error("No solution specified")
798
799 entries = {}
800 entries_deps_content = {}
801
802 # Inner helper to generate base url and rev tuple (including honoring
803 # |revision_overrides|)
804 def GetURLAndRev(name, original_url):
805 if original_url.find("@") < 0:
806 if revision_overrides.has_key(name):
807 return (original_url, int(revision_overrides[name]))
808 else:
809 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000810 return (original_url,
811 gclient_scm.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)
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000827 entries_deps_content[name] = gclient_scm.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)
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000854 deps_parent_content = gclient_scm.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: