blob: d3d7c11c41484e09ed7c80cb6bbce57d711888b2 [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
maruel@chromium.orgba551772010-02-03 18:21:42 +00002# Copyright (c) 2010 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
6"""A wrapper script to manage a set of client modules in different SCM.
7
8This script is intended to be used to help basic management of client
msb@chromium.orgd6504212010-01-13 17:34:31 +00009program sources residing in one or more Subversion modules and Git
10repositories, along with other modules it depends on, also in Subversion or Git,
11but possibly on multiple respositories, making a wrapper system apparently
12necessary.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000013
14Files
15 .gclient : Current client configuration, written by 'config' command.
16 Format is a Python script defining 'solutions', a list whose
17 entries each are maps binding the strings "name" and "url"
18 to strings specifying the name and location of the client
19 module, as well as "custom_deps" to a map similar to the DEPS
20 file below.
21 .gclient_entries : A cache constructed by 'update' command. Format is a
22 Python script defining 'entries', a list of the names
23 of all modules in the client
24 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
25 submodule name to a URL where it can be found (via one SCM)
26
27Hooks
28 .gclient and DEPS files may optionally contain a list named "hooks" to
29 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000030 working copy as a result of a "sync"/"update" or "revert" operation. This
31 could be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000032 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000033 --force, all known hooks will run regardless of the state of the working
34 copy.
35
36 Each item in a "hooks" list is a dict, containing these two keys:
37 "pattern" The associated value is a string containing a regular
38 expression. When a file whose pathname matches the expression
39 is checked out, updated, or reverted, the hook's "action" will
40 run.
41 "action" A list describing a command to run along with its arguments, if
42 any. An action command will run at most one time per gclient
43 invocation, regardless of how many files matched the pattern.
44 The action is executed in the same directory as the .gclient
45 file. If the first item in the list is the string "python",
46 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000047 to run the command. If the list contains string "$matching_files"
48 it will be removed from the list and the list will be extended
49 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000050
51 Example:
52 hooks = [
53 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
54 "action": ["python", "image_indexer.py", "--all"]},
55 ]
56"""
57
maruel@chromium.org79692d62010-05-14 18:57:13 +000058__version__ = "0.3.5"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000059
60import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000061import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import optparse
63import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000064import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000065import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000066import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000067import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000068import urllib
69
maruel@chromium.orgada4c652009-12-03 15:32:01 +000070import breakpad
71
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000072import gclient_scm
73import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000074from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000075
76# default help text
77DEFAULT_USAGE_TEXT = (
msb@chromium.orgd6504212010-01-13 17:34:31 +000078"""usage: %prog <subcommand> [options] [--] [SCM options/args...]
79a wrapper for managing a set of svn client modules and/or git repositories.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080Version """ + __version__ + """
81
82subcommands:
83 cleanup
84 config
85 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000086 export
kbr@google.comab318592009-09-04 00:54:55 +000087 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000088 revert
89 status
90 sync
91 update
92 runhooks
93 revinfo
94
msb@chromium.orgd6504212010-01-13 17:34:31 +000095Options and extra arguments can be passed to invoked SCM commands by
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000096appending them to the command line. Note that if the first such
97appended option starts with a dash (-) then the options must be
98preceded by -- to distinguish them from gclient options.
99
100For additional help on a subcommand or examples of usage, try
101 %prog help <subcommand>
102 %prog help files
103""")
104
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000105
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000106def attr(attr, data):
107 """Sets an attribute on a function."""
108 def hook(fn):
109 setattr(fn, attr, data)
110 return fn
111 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000112
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000113
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000114## GClient implementation.
115
116
117class GClient(object):
118 """Object that represent a gclient checkout."""
119
120 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000121 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
122 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000123 ]
124
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000125 deps_os_choices = {
126 "win32": "win",
127 "win": "win",
128 "cygwin": "win",
129 "darwin": "mac",
130 "mac": "mac",
131 "unix": "unix",
132 "linux": "unix",
133 "linux2": "unix",
134 }
135
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000136 DEPS_FILE = 'DEPS'
137
138 DEFAULT_CLIENT_FILE_TEXT = ("""\
139solutions = [
140 { "name" : "%(solution_name)s",
141 "url" : "%(solution_url)s",
142 "custom_deps" : {
143 },
144 "safesync_url": "%(safesync_url)s"
145 },
146]
147""")
148
149 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
150 { "name" : "%(solution_name)s",
151 "url" : "%(solution_url)s",
152 "custom_deps" : {
153 %(solution_deps)s,
154 },
155 "safesync_url": "%(safesync_url)s"
156 },
157""")
158
159 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
160# Snapshot generated with gclient revinfo --snapshot
161solutions = [
162%(solution_list)s
163]
164""")
165
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000166 def __init__(self, root_dir, options):
167 self._root_dir = root_dir
168 self._options = options
169 self._config_content = None
170 self._config_dict = {}
171 self._deps_hooks = []
172
173 def SetConfig(self, content):
174 self._config_dict = {}
175 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000176 try:
177 exec(content, self._config_dict)
178 except SyntaxError, e:
179 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000180 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000181 # Try to construct a human readable error message
182 error_message = [
183 'There is a syntax error in your configuration file.',
184 'Line #%s, character %s:' % (e.lineno, e.offset),
185 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
186 except:
187 # Something went wrong, re-raise the original exception
188 raise e
189 else:
190 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000191 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000192
193 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000194 gclient_utils.FileWrite(os.path.join(self._root_dir,
195 self._options.config_filename),
196 self._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000197
198 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000199 client_source = gclient_utils.FileRead(
200 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000201 self.SetConfig(client_source)
202
203 def ConfigContent(self):
204 return self._config_content
205
206 def GetVar(self, key, default=None):
207 return self._config_dict.get(key, default)
208
209 @staticmethod
210 def LoadCurrentConfig(options, from_dir=None):
211 """Searches for and loads a .gclient file relative to the current working
212 dir.
213
214 Returns:
215 A dict representing the contents of the .gclient file or an empty dict if
216 the .gclient file doesn't exist.
217 """
218 if not from_dir:
219 from_dir = os.curdir
220 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000221 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000222 split_path = os.path.split(path)
223 if not split_path[1]:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000224 return None
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000225 path = split_path[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000226 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000227 client._LoadConfig()
228 return client
229
230 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000231 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000232 'solution_name': solution_name,
233 'solution_url': solution_url,
234 'safesync_url' : safesync_url,
235 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000236
237 def _SaveEntries(self, entries):
238 """Creates a .gclient_entries file to record the list of unique checkouts.
239
240 The .gclient_entries file lives in the same directory as .gclient.
241
242 Args:
243 entries: A sequence of solution names.
244 """
maruel@chromium.orge41f6822010-04-08 16:37:06 +0000245 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
246 # makes testing a bit too fun.
247 result = pprint.pformat(entries, 2)
248 if result.startswith('{\''):
maruel@chromium.org1edec4d2010-04-08 17:17:06 +0000249 result = '{ \'' + result[2:]
maruel@chromium.orge41f6822010-04-08 16:37:06 +0000250 text = "entries = \\\n" + result + '\n'
msb@chromium.org2e38de72009-09-28 17:04:47 +0000251 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000252 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000253
254 def _ReadEntries(self):
255 """Read the .gclient_entries file for the given client.
256
257 Args:
258 client: The client for which the entries file should be read.
259
260 Returns:
261 A sequence of solution names, which will be empty if there is the
262 entries file hasn't been created yet.
263 """
264 scope = {}
265 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000266 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000267 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000268 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000269 return scope["entries"]
270
271 class FromImpl:
272 """Used to implement the From syntax."""
273
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000274 def __init__(self, module_name, sub_target_name=None):
275 """module_name is the dep module we want to include from. It can also be
276 the name of a subdirectory to include from.
277
278 sub_target_name is an optional parameter if the module name in the other
279 DEPS file is different. E.g., you might want to map src/net to net."""
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000280 self.module_name = module_name
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000281 self.sub_target_name = sub_target_name
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000282
283 def __str__(self):
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000284 return 'From(%s, %s)' % (repr(self.module_name),
285 repr(self.sub_target_name))
286
287 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
288 """Resolve the URL for this From entry."""
289 sub_deps_target_name = target_name
290 if self.sub_target_name:
291 sub_deps_target_name = self.sub_target_name
292 url = sub_deps[sub_deps_target_name]
293 if url.startswith('/'):
294 # If it's a relative URL, we need to resolve the URL relative to the
295 # sub deps base URL.
296 if not isinstance(sub_deps_base_url, basestring):
297 sub_deps_base_url = sub_deps_base_url.GetPath()
298 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
299 None)
300 url = scm.FullUrlForRelativeUrl(url)
301 return url
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000302
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000303 class FileImpl:
304 """Used to implement the File('') syntax which lets you sync a single file
305 from an SVN repo."""
306
307 def __init__(self, file_location):
308 self.file_location = file_location
309
310 def __str__(self):
311 return 'File("%s")' % self.file_location
312
313 def GetPath(self):
314 return os.path.split(self.file_location)[0]
315
316 def GetFilename(self):
317 rev_tokens = self.file_location.split('@')
318 return os.path.split(rev_tokens[0])[1]
319
320 def GetRevision(self):
321 rev_tokens = self.file_location.split('@')
322 if len(rev_tokens) > 1:
323 return rev_tokens[1]
324 return None
325
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000326 class _VarImpl:
327 def __init__(self, custom_vars, local_scope):
328 self._custom_vars = custom_vars
329 self._local_scope = local_scope
330
331 def Lookup(self, var_name):
332 """Implements the Var syntax."""
333 if var_name in self._custom_vars:
334 return self._custom_vars[var_name]
335 elif var_name in self._local_scope.get("vars", {}):
336 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000337 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000338
339 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000340 custom_vars, parse_hooks):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000341 """Parses the DEPS file for the specified solution.
342
343 Args:
344 solution_name: The name of the solution to query.
345 solution_deps_content: Content of the DEPS file for the solution
346 custom_vars: A dict of vars to override any vars defined in the DEPS file.
347
348 Returns:
349 A dict mapping module names (as relative paths) to URLs or an empty
350 dict if the solution does not have a DEPS file.
351 """
352 # Skip empty
353 if not solution_deps_content:
354 return {}
355 # Eval the content
356 local_scope = {}
357 var = self._VarImpl(custom_vars, local_scope)
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000358 global_scope = {
359 "File": self.FileImpl,
360 "From": self.FromImpl,
361 "Var": var.Lookup,
362 "deps_os": {},
363 }
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000364 exec(solution_deps_content, global_scope, local_scope)
365 deps = local_scope.get("deps", {})
366
367 # load os specific dependencies if defined. these dependencies may
368 # override or extend the values defined by the 'deps' member.
369 if "deps_os" in local_scope:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000370
371 if self._options.deps_os is not None:
372 deps_to_include = self._options.deps_os.split(",")
373 if "all" in deps_to_include:
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000374 deps_to_include = list(set(self.deps_os_choices.itervalues()))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000375 else:
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000376 deps_to_include = [self.deps_os_choices.get(sys.platform, "unix")]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000377
378 deps_to_include = set(deps_to_include)
379 for deps_os_key in deps_to_include:
380 os_deps = local_scope["deps_os"].get(deps_os_key, {})
381 if len(deps_to_include) > 1:
382 # Ignore any overrides when including deps for more than one
383 # platform, so we collect the broadest set of dependencies available.
384 # We may end up with the wrong revision of something for our
385 # platform, but this is the best we can do.
386 deps.update([x for x in os_deps.items() if not x[0] in deps])
387 else:
388 deps.update(os_deps)
389
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000390 if 'hooks' in local_scope and parse_hooks:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000391 self._deps_hooks.extend(local_scope['hooks'])
392
393 # If use_relative_paths is set in the DEPS file, regenerate
394 # the dictionary using paths relative to the directory containing
395 # the DEPS file.
396 if local_scope.get('use_relative_paths'):
397 rel_deps = {}
398 for d, url in deps.items():
399 # normpath is required to allow DEPS to use .. in their
400 # dependency local path.
401 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
402 return rel_deps
403 else:
404 return deps
405
406 def _ParseAllDeps(self, solution_urls, solution_deps_content):
407 """Parse the complete list of dependencies for the client.
408
409 Args:
410 solution_urls: A dict mapping module names (as relative paths) to URLs
411 corresponding to the solutions specified by the client. This parameter
412 is passed as an optimization.
413 solution_deps_content: A dict mapping module names to the content
414 of their DEPS files
415
416 Returns:
417 A dict mapping module names (as relative paths) to URLs corresponding
418 to the entire set of dependencies to checkout for the given client.
419
420 Raises:
421 Error: If a dependency conflicts with another dependency or of a solution.
422 """
423 deps = {}
424 for solution in self.GetVar("solutions"):
425 custom_vars = solution.get("custom_vars", {})
426 solution_deps = self._ParseSolutionDeps(
427 solution["name"],
428 solution_deps_content[solution["name"]],
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000429 custom_vars,
430 True)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000431
432 # If a line is in custom_deps, but not in the solution, we want to append
433 # this line to the solution.
434 if "custom_deps" in solution:
435 for d in solution["custom_deps"]:
436 if d not in solution_deps:
437 solution_deps[d] = solution["custom_deps"][d]
438
439 for d in solution_deps:
440 if "custom_deps" in solution and d in solution["custom_deps"]:
441 # Dependency is overriden.
442 url = solution["custom_deps"][d]
443 if url is None:
444 continue
445 else:
446 url = solution_deps[d]
447 # if we have a From reference dependent on another solution, then
448 # just skip the From reference. When we pull deps for the solution,
449 # we will take care of this dependency.
450 #
451 # If multiple solutions all have the same From reference, then we
452 # should only add one to our list of dependencies.
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000453 if isinstance(url, self.FromImpl):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000454 if url.module_name in solution_urls:
455 # Already parsed.
456 continue
457 if d in deps and type(deps[d]) != str:
458 if url.module_name == deps[d].module_name:
459 continue
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000460 elif isinstance(url, str):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000461 parsed_url = urlparse.urlparse(url)
462 scheme = parsed_url[0]
463 if not scheme:
464 # A relative url. Fetch the real base.
465 path = parsed_url[2]
466 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000467 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000468 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000469 # Create a scm just to query the full url.
470 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
471 None)
472 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000473 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000474 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000475 "Solutions have conflicting versions of dependency \"%s\"" % d)
476 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000477 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000478 "Dependency \"%s\" conflicts with specified solution" % d)
479 # Grab the dependency.
480 deps[d] = url
481 return deps
482
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000483 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000484 """Runs the action from a single hook.
485 """
486 command = hook_dict['action'][:]
487 if command[0] == 'python':
488 # If the hook specified "python" as the first item, the action is a
489 # Python script. Run it by starting a new copy of the same
490 # interpreter.
491 command[0] = sys.executable
492
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000493 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000494 splice_index = command.index('$matching_files')
495 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000496
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000497 # Use a discrete exit status code of 2 to indicate that a hook action
498 # failed. Users of this script may wish to treat hook action failures
499 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000500 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000501
502 def _RunHooks(self, command, file_list, is_using_git):
503 """Evaluates all hooks, running actions as needed.
504 """
505 # Hooks only run for these command types.
506 if not command in ('update', 'revert', 'runhooks'):
507 return
508
evan@chromium.org67820ef2009-07-27 17:23:00 +0000509 # Hooks only run when --nohooks is not specified
510 if self._options.nohooks:
511 return
512
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000513 # Get any hooks from the .gclient file.
514 hooks = self.GetVar("hooks", [])
515 # Add any hooks found in DEPS files.
516 hooks.extend(self._deps_hooks)
517
518 # If "--force" was specified, run all hooks regardless of what files have
519 # changed. If the user is using git, then we don't know what files have
520 # changed so we always run all hooks.
521 if self._options.force or is_using_git:
522 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000523 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000524 return
525
526 # Run hooks on the basis of whether the files from the gclient operation
527 # match each hook's pattern.
528 for hook_dict in hooks:
529 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000530 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000531 if matching_file_list:
532 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000533
534 def RunOnDeps(self, command, args):
535 """Runs a command on each dependency in a client and its dependencies.
536
537 The module's dependencies are specified in its top-level DEPS files.
538
539 Args:
540 command: The command to use (e.g., 'status' or 'diff')
541 args: list of str - extra arguments to add to the command line.
542
543 Raises:
544 Error: If the client has conflicting entries.
545 """
546 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000547 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000548
549 # Check for revision overrides.
550 revision_overrides = {}
551 for revision in self._options.revisions:
552 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000553 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000554 "Specify the full dependency when specifying a revision number.")
555 revision_elem = revision.split("@")
556 # Disallow conflicting revs
557 if revision_overrides.has_key(revision_elem[0]) and \
558 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000559 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000560 "Conflicting revision numbers specified.")
561 revision_overrides[revision_elem[0]] = revision_elem[1]
562
563 solutions = self.GetVar("solutions")
564 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000565 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000566
567 # When running runhooks --force, there's no need to consult the SCM.
568 # All known hooks are expected to run unconditionally regardless of working
569 # copy state, so skip the SCM status check.
570 run_scm = not (command == 'runhooks' and self._options.force)
571
572 entries = {}
573 entries_deps_content = {}
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000574 file_list = []
575 # Run on the base solutions first.
576 for solution in solutions:
577 name = solution["name"]
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000578 deps_file = solution.get("deps_file", self.DEPS_FILE)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000579 if '/' in deps_file or '\\' in deps_file:
580 raise gclient_utils.Error('deps_file name must not be a path, just a '
581 'filename.')
582 if name in entries:
583 raise gclient_utils.Error("solution %s specified more than once" % name)
584 url = solution["url"]
585 entries[name] = url
586 if run_scm and url:
587 self._options.revision = revision_overrides.get(name)
588 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
589 scm.RunCommand(command, self._options, args, file_list)
590 file_list = [os.path.join(name, f.strip()) for f in file_list]
591 self._options.revision = None
592 try:
593 deps_content = gclient_utils.FileRead(
594 os.path.join(self._root_dir, name, deps_file))
595 except IOError, e:
596 if e.errno != errno.ENOENT:
597 raise
598 deps_content = ""
599 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000600
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000601 # Process the dependencies next (sort alphanumerically to ensure that
602 # containing directories get populated first and for readability)
603 deps = self._ParseAllDeps(entries, entries_deps_content)
604 deps_to_process = deps.keys()
605 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000606
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000607 # First pass for direct dependencies.
608 if command == 'update' and not self._options.verbose:
609 pm = Progress('Syncing projects', len(deps_to_process))
610 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000611 if command == 'update' and not self._options.verbose:
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000612 pm.update()
613 if type(deps[d]) == str:
614 url = deps[d]
615 entries[d] = url
616 if run_scm:
617 self._options.revision = revision_overrides.get(d)
618 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
619 scm.RunCommand(command, self._options, args, file_list)
620 self._options.revision = None
621 elif isinstance(deps[d], self.FileImpl):
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000622 file_dep = deps[d]
623 self._options.revision = file_dep.GetRevision()
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000624 if run_scm:
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000625 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self._root_dir, d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000626 scm.RunCommand("updatesingle", self._options,
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000627 args + [file_dep.GetFilename()], file_list)
maruel@chromium.org79692d62010-05-14 18:57:13 +0000628
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000629 if command == 'update' and not self._options.verbose:
630 pm.end()
piman@chromium.org6f363722010-04-27 00:41:09 +0000631
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000632 # Second pass for inherited deps (via the From keyword)
633 for d in deps_to_process:
634 if isinstance(deps[d], self.FromImpl):
635 filename = os.path.join(self._root_dir,
636 deps[d].module_name,
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000637 self.DEPS_FILE)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000638 content = gclient_utils.FileRead(filename)
639 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
640 False)
641 # Getting the URL from the sub_deps file can involve having to resolve
642 # a File() or having to resolve a relative URL. To resolve relative
643 # URLs, we need to pass in the orignal sub deps URL.
644 sub_deps_base_url = deps[deps[d].module_name]
645 url = deps[d].GetUrl(d, sub_deps_base_url, self._root_dir, sub_deps)
646 entries[d] = url
647 if run_scm:
648 self._options.revision = revision_overrides.get(d)
649 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
650 scm.RunCommand(command, self._options, args, file_list)
651 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000652
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000653 # Convert all absolute paths to relative.
654 for i in range(len(file_list)):
655 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
656 # It depends on the command being executed (like runhooks vs sync).
657 if not os.path.isabs(file_list[i]):
658 continue
659
660 prefix = os.path.commonprefix([self._root_dir.lower(),
661 file_list[i].lower()])
662 file_list[i] = file_list[i][len(prefix):]
663
664 # Strip any leading path separators.
665 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
666 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000667
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000668 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000669 self._RunHooks(command, file_list, is_using_git)
670
671 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000672 # Notify the user if there is an orphaned entry in their working copy.
673 # Only delete the directory if there are no changes in it, and
674 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000675 prev_entries = self._ReadEntries()
676 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000677 # Fix path separator on Windows.
678 entry_fixed = entry.replace('/', os.path.sep)
679 e_dir = os.path.join(self._root_dir, entry_fixed)
680 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000681 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000682 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000683 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000684 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000685 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000686 else:
687 file_list = []
688 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
689 entry_fixed)
690 scm.status(self._options, [], file_list)
691 modified_files = file_list != []
692 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000693 # There are modified files in this entry. Keep warning until
694 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000695 print(("\nWARNING: \"%s\" is no longer part of this client. "
696 "It is recommended that you manually remove it.\n") %
697 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000698 else:
699 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000700 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000701 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000702 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000703 # record the current list of entries for next time
704 self._SaveEntries(entries)
705
706 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000707 """Output revision info mapping for the client and its dependencies.
708
709 This allows the capture of an overall "revision" for the source tree that
710 can be used to reproduce the same tree in the future. The actual output
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000711 contains enough information (source paths, svn server urls and revisions)
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000712 that it can be used either to generate external svn/git commands (without
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000713 gclient) or as input to gclient's --rev option (with some massaging of
714 the data).
715
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000716 Since we care about the revision of the current source tree, for git
717 repositories this command uses the revision of the HEAD. For subversion we
718 use BASE.
719
720 The --snapshot option allows creating a .gclient file to reproduce the tree.
721
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000722 Raises:
723 Error: If the client has conflicting entries.
724 """
725 # Check for revision overrides.
726 revision_overrides = {}
727 for revision in self._options.revisions:
728 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000729 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000730 "Specify the full dependency when specifying a revision number.")
731 revision_elem = revision.split("@")
732 # Disallow conflicting revs
733 if revision_overrides.has_key(revision_elem[0]) and \
734 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000735 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000736 "Conflicting revision numbers specified.")
737 revision_overrides[revision_elem[0]] = revision_elem[1]
738
739 solutions = self.GetVar("solutions")
740 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000741 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000742
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000743 # Inner helper to generate base url and rev tuple
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000744 def GetURLAndRev(name, original_url):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000745 url, _ = gclient_utils.SplitUrlRevision(original_url)
746 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name)
747 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000748
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000749 # text of the snapshot gclient file
750 new_gclient = ""
751 # Dictionary of { path : SCM url } to ensure no duplicate solutions
752 solution_names = {}
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000753 entries = {}
754 entries_deps_content = {}
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000755 # Run on the base solutions first.
756 for solution in solutions:
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000757 # Dictionary of { path : SCM url } to describe the gclient checkout
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000758 name = solution["name"]
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000759 if name in solution_names:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000760 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000761 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000762 entries[name] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000763 solution_names[name] = "%s@%s" % (url, rev)
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000764 deps_file = solution.get("deps_file", self.DEPS_FILE)
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000765 if '/' in deps_file or '\\' in deps_file:
766 raise gclient_utils.Error('deps_file name must not be a path, just a '
767 'filename.')
768 try:
769 deps_content = gclient_utils.FileRead(
770 os.path.join(self._root_dir, name, deps_file))
771 except IOError, e:
772 if e.errno != errno.ENOENT:
773 raise
774 deps_content = ""
775 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000776
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000777 # Process the dependencies next (sort alphanumerically to ensure that
778 # containing directories get populated first and for readability)
779 deps = self._ParseAllDeps(entries, entries_deps_content)
780 deps_to_process = deps.keys()
781 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000782
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000783 # First pass for direct dependencies.
784 for d in deps_to_process:
785 if type(deps[d]) == str:
786 (url, rev) = GetURLAndRev(d, deps[d])
787 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000788
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000789 # Second pass for inherited deps (via the From keyword)
790 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000791 if isinstance(deps[d], self.FromImpl):
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000792 deps_parent_url = entries[deps[d].module_name]
793 if deps_parent_url.find("@") < 0:
794 raise gclient_utils.Error("From %s missing revisioned url" %
795 deps[d].module_name)
796 content = gclient_utils.FileRead(os.path.join(
797 self._root_dir,
798 deps[d].module_name,
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000799 self.DEPS_FILE))
800 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
801 False)
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000802 (url, rev) = GetURLAndRev(d, sub_deps[d])
803 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000804
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000805 # Build the snapshot configuration string
806 if self._options.snapshot:
807 url = entries.pop(name)
808 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
809 for x in sorted(entries.keys())])
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000810
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000811 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000812 'solution_name': name,
813 'solution_url': url,
814 'safesync_url' : "",
815 'solution_deps': custom_deps,
816 }
817 else:
818 print(";\n".join(["%s: %s" % (x, entries[x])
819 for x in sorted(entries.keys())]))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000820
821 # Print the snapshot configuration file
822 if self._options.snapshot:
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000823 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000824 snapclient = GClient(self._root_dir, self._options)
825 snapclient.SetConfig(config)
826 print(snapclient._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000827
828
829## gclient commands.
830
831
maruel@chromium.org79692d62010-05-14 18:57:13 +0000832def CMDcleanup(options, args):
833 """Clean up all working copies, using 'svn cleanup' for each module.
834Additional options and args may be passed to 'svn cleanup'.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000835
maruel@chromium.org79692d62010-05-14 18:57:13 +0000836usage: cleanup [options] [--] [svn cleanup args/options]
837
838Valid options:
839 --verbose : output additional diagnostics
840"""
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000841 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000842 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000843 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000844 if options.verbose:
845 # Print out the .gclient file. This is longer than if we just printed the
846 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000847 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000848 return client.RunOnDeps('cleanup', args)
849
850
maruel@chromium.org79692d62010-05-14 18:57:13 +0000851def CMDconfig(options, args):
852 """Create a .gclient file in the current directory; this
853specifies the configuration for further commands. After update/sync,
854top-level DEPS files in each module are read to determine dependent
855modules to operate on as well. If optional [url] parameter is
856provided, then configuration is read from a specified Subversion server
857URL. Otherwise, a --spec option must be provided. A --name option overrides
858the default name for the solutions.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000859
maruel@chromium.org79692d62010-05-14 18:57:13 +0000860usage: config [option | url] [safesync url]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000861
maruel@chromium.org79692d62010-05-14 18:57:13 +0000862Valid options:
863 --name path : alternate relative path for the solution
864 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
865 *Note that due to Cygwin/Python brokenness, it
866 probably can't contain any newlines.*
867
868Examples:
869 gclient config https://gclient.googlecode.com/svn/trunk/gclient
870 configure a new client to check out gclient.py tool sources
871 gclient config --name tools https://gclient.googlecode.com/svn/trunk/gclient
872 gclient config --spec='solutions=[{"name":"gclient",
873 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
874 '"custom_deps":{}}]'
875"""
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000876 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000877 raise gclient_utils.Error("required argument missing; see 'gclient help "
878 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000879 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000880 raise gclient_utils.Error("%s file already exists in the current directory"
881 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000882 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000883 if options.spec:
884 client.SetConfig(options.spec)
885 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000886 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000887 if not options.name:
888 name = base_url.split("/")[-1]
889 else:
890 # specify an alternate relpath for the given URL.
891 name = options.name
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000892 safesync_url = ""
893 if len(args) > 1:
894 safesync_url = args[1]
895 client.SetDefaultConfig(name, base_url, safesync_url)
896 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000897 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000898
899
maruel@chromium.org79692d62010-05-14 18:57:13 +0000900def CMDexport(options, args):
901 """Wrapper for svn export for all managed directories
902"""
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000903 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000904 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000905 client = GClient.LoadCurrentConfig(options)
906
907 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000908 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000909
910 if options.verbose:
911 # Print out the .gclient file. This is longer than if we just printed the
912 # client dict, but more legible, and it might contain helpful comments.
913 print(client.ConfigContent())
914 return client.RunOnDeps('export', args)
915
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000916
maruel@chromium.org79692d62010-05-14 18:57:13 +0000917def CMDhelp(options, args):
918 """Describe the usage of this program or its subcommands.
919
920usage: help [options] [subcommand]
921
922Valid options:
923 --verbose : output additional diagnostics
924"""
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000925 __pychecker__ = 'unusednames=options'
maruel@chromium.org79692d62010-05-14 18:57:13 +0000926 module = sys.modules[__name__]
927 commands = [x[3:] for x in dir(module) if x.startswith('CMD')]
928 if len(args) == 1 and args[0] in commands:
929 print getattr(module, 'CMD' + args[0]).__doc__
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000930 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000931 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
932 args[0])
maruel@chromium.org79692d62010-05-14 18:57:13 +0000933 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000934
935
maruel@chromium.org79692d62010-05-14 18:57:13 +0000936def CMDpack(options, args):
937 """Generate a patch which can be applied at the root of the tree.
938Internally, runs 'svn diff' on each checked out module and
939dependencies, and performs minimal postprocessing of the output. The
940resulting patch is printed to stdout and can be applied to a freshly
941checked out tree via 'patch -p0 < patchfile'. Additional args and
942options to 'svn diff' can be passed after gclient options.
kbr@google.comab318592009-09-04 00:54:55 +0000943
maruel@chromium.org79692d62010-05-14 18:57:13 +0000944usage: pack [options] [--] [svn args/options]
945
946Valid options:
947 --verbose : output additional diagnostics
948
949Examples:
950 gclient pack > patch.txt
951 generate simple patch for configured client and dependences
952 gclient pack -- -x -b > patch.txt
953 generate patch using 'svn diff -x -b' to suppress
954 whitespace-only differences
955 gclient pack -- -r HEAD -x -b > patch.txt
956 generate patch, diffing each file versus the latest version of
957 each module
958"""
kbr@google.comab318592009-09-04 00:54:55 +0000959 client = GClient.LoadCurrentConfig(options)
960 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000961 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +0000962 if options.verbose:
963 # Print out the .gclient file. This is longer than if we just printed the
964 # client dict, but more legible, and it might contain helpful comments.
965 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +0000966 return client.RunOnDeps('pack', args)
967
968
maruel@chromium.org79692d62010-05-14 18:57:13 +0000969def CMDstatus(options, args):
970 """Show the status of client and dependent modules, using 'svn diff'
971for each module. Additional options and args may be passed to 'svn diff'.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000972
maruel@chromium.org79692d62010-05-14 18:57:13 +0000973usage: status [options] [--] [svn diff args/options]
974
975Valid options:
976 --verbose : output additional diagnostics
977 --nohooks : don't run the hooks after the update is complete
978"""
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000979 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000980 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000981 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000982 if options.verbose:
983 # Print out the .gclient file. This is longer than if we just printed the
984 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000985 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000986 return client.RunOnDeps('status', args)
987
988
maruel@chromium.org79692d62010-05-14 18:57:13 +0000989def CMDsync(options, args):
990 """Perform a checkout/update of the modules specified by the gclient
991configuration; see 'help config'. Unless --revision is specified,
992then the latest revision of the root solutions is checked out, with
993dependent submodule versions updated according to DEPS files.
994If --revision is specified, then the given revision is used in place
995of the latest, either for a single solution or for all solutions.
996Unless the --force option is provided, solutions and modules whose
997local revision matches the one to update (i.e., they have not changed
998in the repository) are *not* modified. Unless --nohooks is provided,
999the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001000
maruel@chromium.org79692d62010-05-14 18:57:13 +00001001usage: gclient sync [options] [--] [SCM update options/args]
1002
1003Valid options:
1004 --force : force update even for unchanged modules
1005 --nohooks : don't run the hooks after the update is complete
1006 --revision SOLUTION@REV : update given solution to specified revision
1007 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
1008 --verbose : output additional diagnostics
1009 --head : update to latest revision, instead of last good
1010 revision
1011 --reset : resets any local changes before updating (git only)
1012
1013Examples:
1014 gclient sync
1015 update files from SCM according to current configuration,
1016 *for modules which have changed since last update or sync*
1017 gclient sync --force
1018 update files from SCM according to current configuration, for
1019 all modules (useful for recovering files deleted from local copy)
1020 gclient sync --revision src@31000
1021 update src directory to r31000
1022"""
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001023 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001024
1025 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001026 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001027
1028 if not options.head:
1029 solutions = client.GetVar('solutions')
1030 if solutions:
1031 for s in solutions:
1032 if s.get('safesync_url', ''):
1033 # rip through revisions and make sure we're not over-riding
1034 # something that was explicitly passed
1035 has_key = False
1036 for r in options.revisions:
1037 if r.split('@')[0] == s['name']:
1038 has_key = True
1039 break
1040
1041 if not has_key:
1042 handle = urllib.urlopen(s['safesync_url'])
1043 rev = handle.read().strip()
1044 handle.close()
1045 if len(rev):
1046 options.revisions.append(s['name']+'@'+rev)
1047
1048 if options.verbose:
1049 # Print out the .gclient file. This is longer than if we just printed the
1050 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001051 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001052 return client.RunOnDeps('update', args)
1053
1054
maruel@chromium.org79692d62010-05-14 18:57:13 +00001055def CMDupdate(options, args):
1056 """Alias for the sync command. Deprecated.
1057"""
1058 return CMDsync(options, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001059
maruel@chromium.org79692d62010-05-14 18:57:13 +00001060
1061def CMDdiff(options, args):
1062 """Display the differences between two revisions of modules.
1063(Does 'svn diff' for each checked out module and dependences.)
1064Additional args and options to 'svn diff' can be passed after
1065gclient options.
1066
1067usage: diff [options] [--] [svn args/options]
1068
1069Valid options:
1070 --verbose : output additional diagnostics
1071
1072Examples:
1073 gclient diff
1074 simple 'svn diff' for configured client and dependences
1075 gclient diff -- -x -b
1076 use 'svn diff -x -b' to suppress whitespace-only differences
1077 gclient diff -- -r HEAD -x -b
1078 diff versus the latest version of each module
1079"""
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001080 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001081 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001082 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001083 if options.verbose:
1084 # Print out the .gclient file. This is longer than if we just printed the
1085 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001086 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001087 return client.RunOnDeps('diff', args)
1088
1089
maruel@chromium.org79692d62010-05-14 18:57:13 +00001090def CMDrevert(options, args):
1091 """Revert every file in every managed directory in the client view.
1092"""
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001093 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001095 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001096 return client.RunOnDeps('revert', args)
1097
1098
maruel@chromium.org79692d62010-05-14 18:57:13 +00001099def CMDrunhooks(options, args):
1100 """Runs hooks for files that have been modified in the local working copy,
1101according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102
maruel@chromium.org79692d62010-05-14 18:57:13 +00001103usage: runhooks [options]
1104
1105Valid options:
1106 --verbose : output additional diagnostics
1107"""
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001108 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001109 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001110 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001111 if options.verbose:
1112 # Print out the .gclient file. This is longer than if we just printed the
1113 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001114 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001115 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001116 return client.RunOnDeps('runhooks', args)
1117
1118
maruel@chromium.org79692d62010-05-14 18:57:13 +00001119def CMDrevinfo(options, args):
1120 """Outputs source path, server URL and revision information for every
1121dependency in all solutions.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001122
maruel@chromium.org79692d62010-05-14 18:57:13 +00001123usage: revinfo [options]
1124"""
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001125 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001126 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001127 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001128 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001130 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001131
1132
maruel@chromium.org79692d62010-05-14 18:57:13 +00001133def DispatchCommand(command, options, args):
1134 """Dispatches the appropriate subcommand based on command line arguments.
1135"""
1136 module = sys.modules[__name__]
1137 command = getattr(module, 'CMD' + command, None)
1138 if command:
1139 return command(options, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001140 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001141 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1142 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001143
1144
1145def Main(argv):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001146 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1147 version=__version__)
maruel@chromium.orge9e22232010-05-16 23:48:58 +00001148 option_parser.add_option("--force", action="store_true",
1149 help="(update/sync only) force update even "
1150 "for modules which haven't changed")
1151 option_parser.add_option("--nohooks", action="store_true",
1152 help="(update/sync/revert only) prevent the hooks "
1153 "from running")
1154 option_parser.add_option("--revision", action="append", dest="revisions",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001155 metavar="REV", default=[],
maruel@chromium.orge9e22232010-05-16 23:48:58 +00001156 help="(update/sync only) sync to a specific "
1157 "revision, can be used multiple times for "
1158 "each solution, e.g. --revision=src@123, "
1159 "--revision=internal@32")
1160 option_parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
1161 help="(update/sync only) sync deps for the "
1162 "specified (comma-separated) platform(s); "
1163 "'all' will sync all platforms")
1164 option_parser.add_option("--reset", action="store_true",
1165 help="(update/sync only) resets any local changes "
1166 "before updating (git only)")
1167 option_parser.add_option("--spec",
1168 help="(config only) create a gclient file "
1169 "containing the provided string")
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001170 option_parser.add_option("-v", "--verbose", action="count", default=0,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001171 help="produce additional output for diagnostics")
maruel@chromium.orge9e22232010-05-16 23:48:58 +00001172 option_parser.add_option("--manually_grab_svn_rev", action="store_true",
maruel@chromium.org7753d242009-10-07 17:40:24 +00001173 help="Skip svn up whenever possible by requesting "
1174 "actual HEAD revision from the repository")
maruel@chromium.orge9e22232010-05-16 23:48:58 +00001175 option_parser.add_option("--head", action="store_true",
1176 help="skips any safesync_urls specified in "
1177 "configured solutions")
1178 option_parser.add_option("--delete_unversioned_trees", action="store_true",
1179 help="on update, delete any unexpected "
1180 "unversioned trees that are in the checkout")
1181 option_parser.add_option("--snapshot", action="store_true",
1182 help="(revinfo only), create a snapshot file "
1183 "of the current version of all repositories")
1184 option_parser.add_option("--name",
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001185 help="specify alternate relative solution path")
maruel@chromium.orge9e22232010-05-16 23:48:58 +00001186 option_parser.add_option("--gclientfile", metavar="FILENAME",
1187 help="specify an alternate .gclient file")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001188
1189 if len(argv) < 2:
1190 # Users don't need to be told to use the 'help' command.
1191 option_parser.print_help()
1192 return 1
1193 # Add manual support for --version as first argument.
1194 if argv[1] == '--version':
1195 option_parser.print_version()
1196 return 0
1197
1198 # Add manual support for --help as first argument.
1199 if argv[1] == '--help':
1200 argv[1] = 'help'
1201
1202 command = argv[1]
1203 options, args = option_parser.parse_args(argv[2:])
1204
1205 if len(argv) < 3 and command == "help":
1206 option_parser.print_help()
1207 return 0
1208
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001209 if options.verbose > 1:
maruel@chromium.org754960e2009-09-21 12:31:05 +00001210 logging.basicConfig(level=logging.DEBUG)
1211
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001212 # Files used for configuration and state saving.
1213 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001214 if options.gclientfile:
1215 options.config_filename = options.gclientfile
1216 options.entries_filename = options.config_filename + "_entries"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001217 options.deps_file = "DEPS"
1218
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001219 options.platform = sys.platform
1220 return DispatchCommand(command, options, args)
1221
1222
1223if "__main__" == __name__:
1224 try:
1225 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001226 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001227 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001228 result = 1
1229 sys.exit(result)
1230
1231# vim: ts=2:sw=2:tw=80:et: