blob: deedf17d8c5696f2b7134b853821f80b28f57dfe [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
2#
3# Copyright 2008 Google Inc. All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""A wrapper script to manage a set of client modules in different SCM.
18
19This script is intended to be used to help basic management of client
20program sources residing in one or more Subversion modules, along with
21other modules it depends on, also in Subversion, but possibly on
22multiple respositories, making a wrapper system apparently necessary.
23
24Files
25 .gclient : Current client configuration, written by 'config' command.
26 Format is a Python script defining 'solutions', a list whose
27 entries each are maps binding the strings "name" and "url"
28 to strings specifying the name and location of the client
29 module, as well as "custom_deps" to a map similar to the DEPS
30 file below.
31 .gclient_entries : A cache constructed by 'update' command. Format is a
32 Python script defining 'entries', a list of the names
33 of all modules in the client
34 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
35 submodule name to a URL where it can be found (via one SCM)
36
37Hooks
38 .gclient and DEPS files may optionally contain a list named "hooks" to
39 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000040 working copy as a result of a "sync"/"update" or "revert" operation. This
41 could be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000042 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000043 --force, all known hooks will run regardless of the state of the working
44 copy.
45
46 Each item in a "hooks" list is a dict, containing these two keys:
47 "pattern" The associated value is a string containing a regular
48 expression. When a file whose pathname matches the expression
49 is checked out, updated, or reverted, the hook's "action" will
50 run.
51 "action" A list describing a command to run along with its arguments, if
52 any. An action command will run at most one time per gclient
53 invocation, regardless of how many files matched the pattern.
54 The action is executed in the same directory as the .gclient
55 file. If the first item in the list is the string "python",
56 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000057 to run the command. If the list contains string "$matching_files"
58 it will be removed from the list and the list will be extended
59 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060
61 Example:
62 hooks = [
63 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
64 "action": ["python", "image_indexer.py", "--all"]},
65 ]
66"""
67
68__author__ = "darinf@gmail.com (Darin Fisher)"
maruel@chromium.org5df6a462009-08-28 18:52:26 +000069__version__ = "0.3.3"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070
71import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000072import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073import optparse
74import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000075import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076import re
77import stat
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080import urllib
81
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000082import gclient_scm
83import gclient_utils
84from gclient_utils import Error, FileRead, FileWrite
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085
86# default help text
87DEFAULT_USAGE_TEXT = (
88"""usage: %prog <subcommand> [options] [--] [svn options/args...]
89a wrapper for managing a set of client modules in svn.
90Version """ + __version__ + """
91
92subcommands:
93 cleanup
94 config
95 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000096 export
kbr@google.comab318592009-09-04 00:54:55 +000097 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000098 revert
99 status
100 sync
101 update
102 runhooks
103 revinfo
104
105Options and extra arguments can be passed to invoked svn commands by
106appending them to the command line. Note that if the first such
107appended option starts with a dash (-) then the options must be
108preceded by -- to distinguish them from gclient options.
109
110For additional help on a subcommand or examples of usage, try
111 %prog help <subcommand>
112 %prog help files
113""")
114
115GENERIC_UPDATE_USAGE_TEXT = (
116 """Perform a checkout/update of the modules specified by the gclient
117configuration; see 'help config'. Unless --revision is specified,
118then the latest revision of the root solutions is checked out, with
119dependent submodule versions updated according to DEPS files.
120If --revision is specified, then the given revision is used in place
121of the latest, either for a single solution or for all solutions.
122Unless the --force option is provided, solutions and modules whose
123local revision matches the one to update (i.e., they have not changed
evan@chromium.org67820ef2009-07-27 17:23:00 +0000124in the repository) are *not* modified. Unless --nohooks is provided,
125the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000126This a synonym for 'gclient %(alias)s'
127
128usage: gclient %(cmd)s [options] [--] [svn update options/args]
129
130Valid options:
131 --force : force update even for unchanged modules
evan@chromium.org67820ef2009-07-27 17:23:00 +0000132 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000133 --revision REV : update/checkout all solutions with specified revision
134 --revision SOLUTION@REV : update given solution to specified revision
135 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
136 --verbose : output additional diagnostics
maruel@chromium.orgb8b6b872009-06-30 18:50:56 +0000137 --head : update to latest revision, instead of last good revision
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000138
139Examples:
140 gclient %(cmd)s
141 update files from SVN according to current configuration,
142 *for modules which have changed since last update or sync*
143 gclient %(cmd)s --force
144 update files from SVN according to current configuration, for
145 all modules (useful for recovering files deleted from local copy)
146""")
147
148COMMAND_USAGE_TEXT = {
149 "cleanup":
150 """Clean up all working copies, using 'svn cleanup' for each module.
151Additional options and args may be passed to 'svn cleanup'.
152
153usage: cleanup [options] [--] [svn cleanup args/options]
154
155Valid options:
156 --verbose : output additional diagnostics
157""",
158 "config": """Create a .gclient file in the current directory; this
159specifies the configuration for further commands. After update/sync,
160top-level DEPS files in each module are read to determine dependent
161modules to operate on as well. If optional [url] parameter is
162provided, then configuration is read from a specified Subversion server
163URL. Otherwise, a --spec option must be provided.
164
165usage: config [option | url] [safesync url]
166
167Valid options:
168 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
169 *Note that due to Cygwin/Python brokenness, it
170 probably can't contain any newlines.*
171
172Examples:
173 gclient config https://gclient.googlecode.com/svn/trunk/gclient
174 configure a new client to check out gclient.py tool sources
175 gclient config --spec='solutions=[{"name":"gclient","""
176 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
177 '"custom_deps":{}}]',
178 "diff": """Display the differences between two revisions of modules.
179(Does 'svn diff' for each checked out module and dependences.)
180Additional args and options to 'svn diff' can be passed after
181gclient options.
182
183usage: diff [options] [--] [svn args/options]
184
185Valid options:
186 --verbose : output additional diagnostics
187
188Examples:
189 gclient diff
190 simple 'svn diff' for configured client and dependences
191 gclient diff -- -x -b
192 use 'svn diff -x -b' to suppress whitespace-only differences
193 gclient diff -- -r HEAD -x -b
194 diff versus the latest version of each module
195""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000196 "export":
197 """Wrapper for svn export for all managed directories
198""",
kbr@google.comab318592009-09-04 00:54:55 +0000199 "pack":
200
201 """Generate a patch which can be applied at the root of the tree.
202Internally, runs 'svn diff' on each checked out module and
203dependencies, and performs minimal postprocessing of the output. The
204resulting patch is printed to stdout and can be applied to a freshly
205checked out tree via 'patch -p0 < patchfile'. Additional args and
206options to 'svn diff' can be passed after gclient options.
207
208usage: pack [options] [--] [svn args/options]
209
210Valid options:
211 --verbose : output additional diagnostics
212
213Examples:
214 gclient pack > patch.txt
215 generate simple patch for configured client and dependences
216 gclient pack -- -x -b > patch.txt
217 generate patch using 'svn diff -x -b' to suppress
218 whitespace-only differences
219 gclient pack -- -r HEAD -x -b > patch.txt
220 generate patch, diffing each file versus the latest version of
221 each module
222""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000223 "revert":
224 """Revert every file in every managed directory in the client view.
225
226usage: revert
227""",
228 "status":
229 """Show the status of client and dependent modules, using 'svn diff'
230for each module. Additional options and args may be passed to 'svn diff'.
231
232usage: status [options] [--] [svn diff args/options]
233
234Valid options:
235 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000236 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000237""",
238 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
239 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
240 "help": """Describe the usage of this program or its subcommands.
241
242usage: help [options] [subcommand]
243
244Valid options:
245 --verbose : output additional diagnostics
246""",
247 "runhooks":
248 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000249according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000250
251usage: runhooks [options]
252
253Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000254 --verbose : output additional diagnostics
255""",
256 "revinfo":
257 """Outputs source path, server URL and revision information for every
258dependency in all solutions (no local checkout required).
259
260usage: revinfo [options]
261""",
262}
263
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000264DEFAULT_CLIENT_FILE_TEXT = ("""\
265# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000266# that will be checked out into your working copy. Each solution may
267# optionally define additional dependencies (via its DEPS file) to be
268# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000269# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000270# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000271# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000272# a text file which contains nothing but the last known good SCM revision to
273# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000274
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000275solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000276 { "name" : "%(solution_name)s",
277 "url" : "%(solution_url)s",
278 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000279 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000280 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000281 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000282 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000283 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000284 "safesync_url": "%(safesync_url)s"
285 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000286]
287""")
288
289
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000290## GClient implementation.
291
292
293class GClient(object):
294 """Object that represent a gclient checkout."""
295
296 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000297 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
298 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000299 ]
300
301 def __init__(self, root_dir, options):
302 self._root_dir = root_dir
303 self._options = options
304 self._config_content = None
305 self._config_dict = {}
306 self._deps_hooks = []
307
308 def SetConfig(self, content):
309 self._config_dict = {}
310 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000311 try:
312 exec(content, self._config_dict)
313 except SyntaxError, e:
314 try:
315 # Try to construct a human readable error message
316 error_message = [
317 'There is a syntax error in your configuration file.',
318 'Line #%s, character %s:' % (e.lineno, e.offset),
319 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
320 except:
321 # Something went wrong, re-raise the original exception
322 raise e
323 else:
324 # Raise a new exception with the human readable message:
325 raise Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000326
327 def SaveConfig(self):
328 FileWrite(os.path.join(self._root_dir, self._options.config_filename),
329 self._config_content)
330
331 def _LoadConfig(self):
332 client_source = FileRead(os.path.join(self._root_dir,
333 self._options.config_filename))
334 self.SetConfig(client_source)
335
336 def ConfigContent(self):
337 return self._config_content
338
339 def GetVar(self, key, default=None):
340 return self._config_dict.get(key, default)
341
342 @staticmethod
343 def LoadCurrentConfig(options, from_dir=None):
344 """Searches for and loads a .gclient file relative to the current working
345 dir.
346
347 Returns:
348 A dict representing the contents of the .gclient file or an empty dict if
349 the .gclient file doesn't exist.
350 """
351 if not from_dir:
352 from_dir = os.curdir
353 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000354 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000355 next = os.path.split(path)
356 if not next[1]:
357 return None
358 path = next[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000359 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000360 client._LoadConfig()
361 return client
362
363 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000364 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
365 'solution_name': solution_name,
366 'solution_url': solution_url,
367 'safesync_url' : safesync_url,
368 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000369
370 def _SaveEntries(self, entries):
371 """Creates a .gclient_entries file to record the list of unique checkouts.
372
373 The .gclient_entries file lives in the same directory as .gclient.
374
375 Args:
376 entries: A sequence of solution names.
377 """
msb@chromium.org2e38de72009-09-28 17:04:47 +0000378 text = "entries = \\\n" + pprint.pformat(entries, 2) + '\n'
379 file_path = os.path.join(self._root_dir, self._options.entries_filename)
380 FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000381
382 def _ReadEntries(self):
383 """Read the .gclient_entries file for the given client.
384
385 Args:
386 client: The client for which the entries file should be read.
387
388 Returns:
389 A sequence of solution names, which will be empty if there is the
390 entries file hasn't been created yet.
391 """
392 scope = {}
393 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000394 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000395 return []
396 exec(FileRead(filename), scope)
397 return scope["entries"]
398
399 class FromImpl:
400 """Used to implement the From syntax."""
401
402 def __init__(self, module_name):
403 self.module_name = module_name
404
405 def __str__(self):
406 return 'From("%s")' % self.module_name
407
408 class _VarImpl:
409 def __init__(self, custom_vars, local_scope):
410 self._custom_vars = custom_vars
411 self._local_scope = local_scope
412
413 def Lookup(self, var_name):
414 """Implements the Var syntax."""
415 if var_name in self._custom_vars:
416 return self._custom_vars[var_name]
417 elif var_name in self._local_scope.get("vars", {}):
418 return self._local_scope["vars"][var_name]
419 raise Error("Var is not defined: %s" % var_name)
420
421 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
422 custom_vars):
423 """Parses the DEPS file for the specified solution.
424
425 Args:
426 solution_name: The name of the solution to query.
427 solution_deps_content: Content of the DEPS file for the solution
428 custom_vars: A dict of vars to override any vars defined in the DEPS file.
429
430 Returns:
431 A dict mapping module names (as relative paths) to URLs or an empty
432 dict if the solution does not have a DEPS file.
433 """
434 # Skip empty
435 if not solution_deps_content:
436 return {}
437 # Eval the content
438 local_scope = {}
439 var = self._VarImpl(custom_vars, local_scope)
440 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
441 exec(solution_deps_content, global_scope, local_scope)
442 deps = local_scope.get("deps", {})
443
444 # load os specific dependencies if defined. these dependencies may
445 # override or extend the values defined by the 'deps' member.
446 if "deps_os" in local_scope:
447 deps_os_choices = {
448 "win32": "win",
449 "win": "win",
450 "cygwin": "win",
451 "darwin": "mac",
452 "mac": "mac",
453 "unix": "unix",
454 "linux": "unix",
455 "linux2": "unix",
456 }
457
458 if self._options.deps_os is not None:
459 deps_to_include = self._options.deps_os.split(",")
460 if "all" in deps_to_include:
461 deps_to_include = deps_os_choices.values()
462 else:
463 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
464
465 deps_to_include = set(deps_to_include)
466 for deps_os_key in deps_to_include:
467 os_deps = local_scope["deps_os"].get(deps_os_key, {})
468 if len(deps_to_include) > 1:
469 # Ignore any overrides when including deps for more than one
470 # platform, so we collect the broadest set of dependencies available.
471 # We may end up with the wrong revision of something for our
472 # platform, but this is the best we can do.
473 deps.update([x for x in os_deps.items() if not x[0] in deps])
474 else:
475 deps.update(os_deps)
476
477 if 'hooks' in local_scope:
478 self._deps_hooks.extend(local_scope['hooks'])
479
480 # If use_relative_paths is set in the DEPS file, regenerate
481 # the dictionary using paths relative to the directory containing
482 # the DEPS file.
483 if local_scope.get('use_relative_paths'):
484 rel_deps = {}
485 for d, url in deps.items():
486 # normpath is required to allow DEPS to use .. in their
487 # dependency local path.
488 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
489 return rel_deps
490 else:
491 return deps
492
493 def _ParseAllDeps(self, solution_urls, solution_deps_content):
494 """Parse the complete list of dependencies for the client.
495
496 Args:
497 solution_urls: A dict mapping module names (as relative paths) to URLs
498 corresponding to the solutions specified by the client. This parameter
499 is passed as an optimization.
500 solution_deps_content: A dict mapping module names to the content
501 of their DEPS files
502
503 Returns:
504 A dict mapping module names (as relative paths) to URLs corresponding
505 to the entire set of dependencies to checkout for the given client.
506
507 Raises:
508 Error: If a dependency conflicts with another dependency or of a solution.
509 """
510 deps = {}
511 for solution in self.GetVar("solutions"):
512 custom_vars = solution.get("custom_vars", {})
513 solution_deps = self._ParseSolutionDeps(
514 solution["name"],
515 solution_deps_content[solution["name"]],
516 custom_vars)
517
518 # If a line is in custom_deps, but not in the solution, we want to append
519 # this line to the solution.
520 if "custom_deps" in solution:
521 for d in solution["custom_deps"]:
522 if d not in solution_deps:
523 solution_deps[d] = solution["custom_deps"][d]
524
525 for d in solution_deps:
526 if "custom_deps" in solution and d in solution["custom_deps"]:
527 # Dependency is overriden.
528 url = solution["custom_deps"][d]
529 if url is None:
530 continue
531 else:
532 url = solution_deps[d]
533 # if we have a From reference dependent on another solution, then
534 # just skip the From reference. When we pull deps for the solution,
535 # we will take care of this dependency.
536 #
537 # If multiple solutions all have the same From reference, then we
538 # should only add one to our list of dependencies.
539 if type(url) != str:
540 if url.module_name in solution_urls:
541 # Already parsed.
542 continue
543 if d in deps and type(deps[d]) != str:
544 if url.module_name == deps[d].module_name:
545 continue
546 else:
547 parsed_url = urlparse.urlparse(url)
548 scheme = parsed_url[0]
549 if not scheme:
550 # A relative url. Fetch the real base.
551 path = parsed_url[2]
552 if path[0] != "/":
553 raise Error(
554 "relative DEPS entry \"%s\" must begin with a slash" % d)
555 # Create a scm just to query the full url.
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000556 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000557 None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000558 url = scm.FullUrlForRelativeUrl(url)
559 if d in deps and deps[d] != url:
560 raise Error(
561 "Solutions have conflicting versions of dependency \"%s\"" % d)
562 if d in solution_urls and solution_urls[d] != url:
563 raise Error(
564 "Dependency \"%s\" conflicts with specified solution" % d)
565 # Grab the dependency.
566 deps[d] = url
567 return deps
568
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000569 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000570 """Runs the action from a single hook.
571 """
572 command = hook_dict['action'][:]
573 if command[0] == 'python':
574 # If the hook specified "python" as the first item, the action is a
575 # Python script. Run it by starting a new copy of the same
576 # interpreter.
577 command[0] = sys.executable
578
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000579 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000580 splice_index = command.index('$matching_files')
581 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000582
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000583 # Use a discrete exit status code of 2 to indicate that a hook action
584 # failed. Users of this script may wish to treat hook action failures
585 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000586 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000587
588 def _RunHooks(self, command, file_list, is_using_git):
589 """Evaluates all hooks, running actions as needed.
590 """
591 # Hooks only run for these command types.
592 if not command in ('update', 'revert', 'runhooks'):
593 return
594
evan@chromium.org67820ef2009-07-27 17:23:00 +0000595 # Hooks only run when --nohooks is not specified
596 if self._options.nohooks:
597 return
598
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000599 # Get any hooks from the .gclient file.
600 hooks = self.GetVar("hooks", [])
601 # Add any hooks found in DEPS files.
602 hooks.extend(self._deps_hooks)
603
604 # If "--force" was specified, run all hooks regardless of what files have
605 # changed. If the user is using git, then we don't know what files have
606 # changed so we always run all hooks.
607 if self._options.force or is_using_git:
608 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000609 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000610 return
611
612 # Run hooks on the basis of whether the files from the gclient operation
613 # match each hook's pattern.
614 for hook_dict in hooks:
615 pattern = re.compile(hook_dict['pattern'])
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000616 matching_file_list = [file for file in file_list if pattern.search(file)]
617 if matching_file_list:
618 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000619
620 def RunOnDeps(self, command, args):
621 """Runs a command on each dependency in a client and its dependencies.
622
623 The module's dependencies are specified in its top-level DEPS files.
624
625 Args:
626 command: The command to use (e.g., 'status' or 'diff')
627 args: list of str - extra arguments to add to the command line.
628
629 Raises:
630 Error: If the client has conflicting entries.
631 """
632 if not command in self.supported_commands:
633 raise Error("'%s' is an unsupported command" % command)
634
635 # Check for revision overrides.
636 revision_overrides = {}
637 for revision in self._options.revisions:
638 if revision.find("@") == -1:
639 raise Error(
640 "Specify the full dependency when specifying a revision number.")
641 revision_elem = revision.split("@")
642 # Disallow conflicting revs
643 if revision_overrides.has_key(revision_elem[0]) and \
644 revision_overrides[revision_elem[0]] != revision_elem[1]:
645 raise Error(
646 "Conflicting revision numbers specified.")
647 revision_overrides[revision_elem[0]] = revision_elem[1]
648
649 solutions = self.GetVar("solutions")
650 if not solutions:
651 raise Error("No solution specified")
652
653 # When running runhooks --force, there's no need to consult the SCM.
654 # All known hooks are expected to run unconditionally regardless of working
655 # copy state, so skip the SCM status check.
656 run_scm = not (command == 'runhooks' and self._options.force)
657
658 entries = {}
659 entries_deps_content = {}
660 file_list = []
661 # Run on the base solutions first.
662 for solution in solutions:
663 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000664 deps_file = solution.get("deps_file", self._options.deps_file)
665 if '/' in deps_file or '\\' in deps_file:
666 raise Error("deps_file name must not be a path, just a filename.")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000667 if name in entries:
668 raise Error("solution %s specified more than once" % name)
669 url = solution["url"]
670 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000671 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000672 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000673 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000674 scm.RunCommand(command, self._options, args, file_list)
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000675 file_list = [os.path.join(name, file.strip()) for file in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000676 self._options.revision = None
677 try:
678 deps_content = FileRead(os.path.join(self._root_dir, name,
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000679 deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000680 except IOError, e:
681 if e.errno != errno.ENOENT:
682 raise
683 deps_content = ""
684 entries_deps_content[name] = deps_content
685
686 # Process the dependencies next (sort alphanumerically to ensure that
687 # containing directories get populated first and for readability)
688 deps = self._ParseAllDeps(entries, entries_deps_content)
689 deps_to_process = deps.keys()
690 deps_to_process.sort()
691
692 # First pass for direct dependencies.
693 for d in deps_to_process:
694 if type(deps[d]) == str:
695 url = deps[d]
696 entries[d] = url
697 if run_scm:
698 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000699 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000700 scm.RunCommand(command, self._options, args, file_list)
701 self._options.revision = None
702
703 # Second pass for inherited deps (via the From keyword)
704 for d in deps_to_process:
705 if type(deps[d]) != str:
706 sub_deps = self._ParseSolutionDeps(
707 deps[d].module_name,
708 FileRead(os.path.join(self._root_dir,
709 deps[d].module_name,
710 self._options.deps_file)),
711 {})
712 url = sub_deps[d]
713 entries[d] = url
714 if run_scm:
715 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000716 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000717 scm.RunCommand(command, self._options, args, file_list)
718 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000719
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000720 # Convert all absolute paths to relative.
721 for i in range(len(file_list)):
722 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
723 # It depends on the command being executed (like runhooks vs sync).
724 if not os.path.isabs(file_list[i]):
725 continue
726
727 prefix = os.path.commonprefix([self._root_dir.lower(),
728 file_list[i].lower()])
729 file_list[i] = file_list[i][len(prefix):]
730
731 # Strip any leading path separators.
732 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
733 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000734
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000735 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000736 self._RunHooks(command, file_list, is_using_git)
737
738 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000739 # Notify the user if there is an orphaned entry in their working copy.
740 # Only delete the directory if there are no changes in it, and
741 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000742 prev_entries = self._ReadEntries()
743 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000744 # Fix path separator on Windows.
745 entry_fixed = entry.replace('/', os.path.sep)
746 e_dir = os.path.join(self._root_dir, entry_fixed)
747 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000748 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000749 modified_files = False
750 if isinstance(prev_entries,list):
751 # old .gclient_entries format was list, now dict
752 modified_files = gclient_scm.CaptureSVNStatus(e_dir)
753 else:
754 file_list = []
755 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
756 entry_fixed)
757 scm.status(self._options, [], file_list)
758 modified_files = file_list != []
759 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000760 # There are modified files in this entry. Keep warning until
761 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000762 print(("\nWARNING: \"%s\" is no longer part of this client. "
763 "It is recommended that you manually remove it.\n") %
764 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000765 else:
766 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000767 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000768 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000769 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000770 # record the current list of entries for next time
771 self._SaveEntries(entries)
772
773 def PrintRevInfo(self):
774 """Output revision info mapping for the client and its dependencies. This
775 allows the capture of a overall "revision" for the source tree that can
776 be used to reproduce the same tree in the future. The actual output
777 contains enough information (source paths, svn server urls and revisions)
778 that it can be used either to generate external svn commands (without
779 gclient) or as input to gclient's --rev option (with some massaging of
780 the data).
781
782 NOTE: Unlike RunOnDeps this does not require a local checkout and is run
783 on the Pulse master. It MUST NOT execute hooks.
784
785 Raises:
786 Error: If the client has conflicting entries.
787 """
788 # Check for revision overrides.
789 revision_overrides = {}
790 for revision in self._options.revisions:
791 if revision.find("@") < 0:
792 raise Error(
793 "Specify the full dependency when specifying a revision number.")
794 revision_elem = revision.split("@")
795 # Disallow conflicting revs
796 if revision_overrides.has_key(revision_elem[0]) and \
797 revision_overrides[revision_elem[0]] != revision_elem[1]:
798 raise Error(
799 "Conflicting revision numbers specified.")
800 revision_overrides[revision_elem[0]] = revision_elem[1]
801
802 solutions = self.GetVar("solutions")
803 if not solutions:
804 raise Error("No solution specified")
805
806 entries = {}
807 entries_deps_content = {}
808
809 # Inner helper to generate base url and rev tuple (including honoring
810 # |revision_overrides|)
811 def GetURLAndRev(name, original_url):
812 if original_url.find("@") < 0:
813 if revision_overrides.has_key(name):
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000814 return (original_url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000815 else:
816 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000817 return (original_url,
818 gclient_scm.CaptureSVNHeadRevision(original_url))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000819 else:
820 url_components = original_url.split("@")
821 if revision_overrides.has_key(name):
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000822 return (url_components[0], revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000823 else:
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000824 return (url_components[0], url_components[1])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000825
826 # Run on the base solutions first.
827 for solution in solutions:
828 name = solution["name"]
829 if name in entries:
830 raise Error("solution %s specified more than once" % name)
831 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000832 entries[name] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000833 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000834 entries_deps_content[name] = gclient_scm.CaptureSVN(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000835 ["cat",
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000836 "%s/%s@%s" % (url,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000837 self._options.deps_file,
838 rev)],
839 os.getcwd())
840
841 # Process the dependencies next (sort alphanumerically to ensure that
842 # containing directories get populated first and for readability)
843 deps = self._ParseAllDeps(entries, entries_deps_content)
844 deps_to_process = deps.keys()
845 deps_to_process.sort()
846
847 # First pass for direct dependencies.
848 for d in deps_to_process:
849 if type(deps[d]) == str:
850 (url, rev) = GetURLAndRev(d, deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000851 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000852
853 # Second pass for inherited deps (via the From keyword)
854 for d in deps_to_process:
855 if type(deps[d]) != str:
856 deps_parent_url = entries[deps[d].module_name]
857 if deps_parent_url.find("@") < 0:
858 raise Error("From %s missing revisioned url" % deps[d].module_name)
859 deps_parent_url_components = deps_parent_url.split("@")
860 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org167b9e62009-09-17 17:41:02 +0000861 deps_parent_content = gclient_scm.CaptureSVN(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000862 ["cat",
863 "%s/%s@%s" % (deps_parent_url_components[0],
864 self._options.deps_file,
865 deps_parent_url_components[1])],
866 os.getcwd())
867 sub_deps = self._ParseSolutionDeps(
868 deps[d].module_name,
869 FileRead(os.path.join(self._root_dir,
870 deps[d].module_name,
871 self._options.deps_file)),
872 {})
873 (url, rev) = GetURLAndRev(d, sub_deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000874 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.org57e893e2009-08-19 18:12:09 +0000875 print(";\n\n".join(["%s: %s" % (x, entries[x])
876 for x in sorted(entries.keys())]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000877
878
879## gclient commands.
880
881
882def DoCleanup(options, args):
883 """Handle the cleanup subcommand.
884
885 Raises:
886 Error: if client isn't configured properly.
887 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000888 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000889 if not client:
890 raise Error("client not configured; see 'gclient config'")
891 if options.verbose:
892 # Print out the .gclient file. This is longer than if we just printed the
893 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000894 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000895 return client.RunOnDeps('cleanup', args)
896
897
898def DoConfig(options, args):
899 """Handle the config subcommand.
900
901 Args:
902 options: If options.spec set, a string providing contents of config file.
903 args: The command line args. If spec is not set,
904 then args[0] is a string URL to get for config file.
905
906 Raises:
907 Error: on usage error
908 """
909 if len(args) < 1 and not options.spec:
910 raise Error("required argument missing; see 'gclient help config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000911 if os.path.exists(options.config_filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000912 raise Error("%s file already exists in the current directory" %
913 options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000914 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000915 if options.spec:
916 client.SetConfig(options.spec)
917 else:
918 # TODO(darin): it would be nice to be able to specify an alternate relpath
919 # for the given URL.
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000920 base_url = args[0].rstrip('/')
921 name = base_url.split("/")[-1]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000922 safesync_url = ""
923 if len(args) > 1:
924 safesync_url = args[1]
925 client.SetDefaultConfig(name, base_url, safesync_url)
926 client.SaveConfig()
927
928
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000929def DoExport(options, args):
930 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000931
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000932 Raises:
933 Error: on usage error
934 """
935 if len(args) != 1:
936 raise Error("Need directory name")
937 client = GClient.LoadCurrentConfig(options)
938
939 if not client:
940 raise Error("client not configured; see 'gclient config'")
941
942 if options.verbose:
943 # Print out the .gclient file. This is longer than if we just printed the
944 # client dict, but more legible, and it might contain helpful comments.
945 print(client.ConfigContent())
946 return client.RunOnDeps('export', args)
947
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000948def DoHelp(options, args):
949 """Handle the help subcommand giving help for another subcommand.
950
951 Raises:
952 Error: if the command is unknown.
953 """
954 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000955 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000956 else:
957 raise Error("unknown subcommand '%s'; see 'gclient help'" % args[0])
958
959
kbr@google.comab318592009-09-04 00:54:55 +0000960def DoPack(options, args):
961 """Handle the pack subcommand.
962
963 Raises:
964 Error: if client isn't configured properly.
965 """
966 client = GClient.LoadCurrentConfig(options)
967 if not client:
968 raise Error("client not configured; see 'gclient config'")
969 if options.verbose:
970 # Print out the .gclient file. This is longer than if we just printed the
971 # client dict, but more legible, and it might contain helpful comments.
972 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +0000973 return client.RunOnDeps('pack', args)
974
975
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000976def DoStatus(options, args):
977 """Handle the status subcommand.
978
979 Raises:
980 Error: if client isn't configured properly.
981 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000982 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000983 if not client:
984 raise Error("client not configured; see 'gclient config'")
985 if options.verbose:
986 # Print out the .gclient file. This is longer than if we just printed the
987 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000988 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000989 return client.RunOnDeps('status', args)
990
991
992def DoUpdate(options, args):
993 """Handle the update and sync subcommands.
994
995 Raises:
996 Error: if client isn't configured properly.
997 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000998 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000999
1000 if not client:
1001 raise Error("client not configured; see 'gclient config'")
1002
1003 if not options.head:
1004 solutions = client.GetVar('solutions')
1005 if solutions:
1006 for s in solutions:
1007 if s.get('safesync_url', ''):
1008 # rip through revisions and make sure we're not over-riding
1009 # something that was explicitly passed
1010 has_key = False
1011 for r in options.revisions:
1012 if r.split('@')[0] == s['name']:
1013 has_key = True
1014 break
1015
1016 if not has_key:
1017 handle = urllib.urlopen(s['safesync_url'])
1018 rev = handle.read().strip()
1019 handle.close()
1020 if len(rev):
1021 options.revisions.append(s['name']+'@'+rev)
1022
1023 if options.verbose:
1024 # Print out the .gclient file. This is longer than if we just printed the
1025 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001026 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001027 return client.RunOnDeps('update', args)
1028
1029
1030def DoDiff(options, args):
1031 """Handle the diff subcommand.
1032
1033 Raises:
1034 Error: if client isn't configured properly.
1035 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001036 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001037 if not client:
1038 raise Error("client not configured; see 'gclient config'")
1039 if options.verbose:
1040 # Print out the .gclient file. This is longer than if we just printed the
1041 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001042 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001043 return client.RunOnDeps('diff', args)
1044
1045
1046def DoRevert(options, args):
1047 """Handle the revert subcommand.
1048
1049 Raises:
1050 Error: if client isn't configured properly.
1051 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001052 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001053 if not client:
1054 raise Error("client not configured; see 'gclient config'")
1055 return client.RunOnDeps('revert', args)
1056
1057
1058def DoRunHooks(options, args):
1059 """Handle the runhooks subcommand.
1060
1061 Raises:
1062 Error: if client isn't configured properly.
1063 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001064 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001065 if not client:
1066 raise Error("client not configured; see 'gclient config'")
1067 if options.verbose:
1068 # Print out the .gclient file. This is longer than if we just printed the
1069 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001070 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001071 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001072 return client.RunOnDeps('runhooks', args)
1073
1074
1075def DoRevInfo(options, args):
1076 """Handle the revinfo subcommand.
1077
1078 Raises:
1079 Error: if client isn't configured properly.
1080 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001081 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001082 if not client:
1083 raise Error("client not configured; see 'gclient config'")
1084 client.PrintRevInfo()
1085
1086
1087gclient_command_map = {
1088 "cleanup": DoCleanup,
1089 "config": DoConfig,
1090 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001091 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001092 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001093 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094 "status": DoStatus,
1095 "sync": DoUpdate,
1096 "update": DoUpdate,
1097 "revert": DoRevert,
1098 "runhooks": DoRunHooks,
1099 "revinfo" : DoRevInfo,
1100}
1101
1102
1103def DispatchCommand(command, options, args, command_map=None):
1104 """Dispatches the appropriate subcommand based on command line arguments."""
1105 if command_map is None:
1106 command_map = gclient_command_map
1107
1108 if command in command_map:
1109 return command_map[command](options, args)
1110 else:
1111 raise Error("unknown subcommand '%s'; see 'gclient help'" % command)
1112
1113
1114def Main(argv):
1115 """Parse command line arguments and dispatch command."""
1116
1117 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1118 version=__version__)
1119 option_parser.disable_interspersed_args()
1120 option_parser.add_option("", "--force", action="store_true", default=False,
1121 help=("(update/sync only) force update even "
1122 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001123 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1124 help=("(update/sync/revert only) prevent the hooks from "
1125 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001126 option_parser.add_option("", "--revision", action="append", dest="revisions",
1127 metavar="REV", default=[],
1128 help=("(update/sync only) sync to a specific "
1129 "revision, can be used multiple times for "
1130 "each solution, e.g. --revision=src@123, "
1131 "--revision=internal@32"))
1132 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1133 metavar="OS_LIST",
1134 help=("(update/sync only) sync deps for the "
1135 "specified (comma-separated) platform(s); "
1136 "'all' will sync all platforms"))
1137 option_parser.add_option("", "--spec", default=None,
1138 help=("(config only) create a gclient file "
1139 "containing the provided string"))
1140 option_parser.add_option("", "--verbose", action="store_true", default=False,
1141 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001142 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1143 default=False,
1144 help="Skip svn up whenever possible by requesting "
1145 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001146 option_parser.add_option("", "--head", action="store_true", default=False,
1147 help=("skips any safesync_urls specified in "
1148 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001149 option_parser.add_option("", "--delete_unversioned_trees",
1150 action="store_true", default=False,
1151 help=("on update, delete any unexpected "
1152 "unversioned trees that are in the checkout"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001153
1154 if len(argv) < 2:
1155 # Users don't need to be told to use the 'help' command.
1156 option_parser.print_help()
1157 return 1
1158 # Add manual support for --version as first argument.
1159 if argv[1] == '--version':
1160 option_parser.print_version()
1161 return 0
1162
1163 # Add manual support for --help as first argument.
1164 if argv[1] == '--help':
1165 argv[1] = 'help'
1166
1167 command = argv[1]
1168 options, args = option_parser.parse_args(argv[2:])
1169
1170 if len(argv) < 3 and command == "help":
1171 option_parser.print_help()
1172 return 0
1173
maruel@chromium.org754960e2009-09-21 12:31:05 +00001174 if options.verbose:
1175 logging.basicConfig(level=logging.DEBUG)
1176
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001177 # Files used for configuration and state saving.
1178 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
1179 options.entries_filename = ".gclient_entries"
1180 options.deps_file = "DEPS"
1181
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001182 options.platform = sys.platform
1183 return DispatchCommand(command, options, args)
1184
1185
1186if "__main__" == __name__:
1187 try:
1188 result = Main(sys.argv)
1189 except Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001190 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001191 result = 1
1192 sys.exit(result)
1193
1194# vim: ts=2:sw=2:tw=80:et: