blob: 6986bb7eb4bbdea5282e529427cf6925e669dc8d [file] [log] [blame]
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +00001# Copyright (c) 2009 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00004
maruel@chromium.orgd5800f12009-11-12 20:03:43 +00005"""Gclient-specific SCM-specific operations."""
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00006
maruel@chromium.org754960e2009-09-21 12:31:05 +00007import logging
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00008import os
maruel@chromium.orgee4071d2009-12-22 22:25:37 +00009import posixpath
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000010import re
11import subprocess
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000012
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000013import scm
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000014import gclient_utils
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000015
16
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000017class DiffFilterer(object):
18 """Simple class which tracks which file is being diffed and
19 replaces instances of its file name in the original and
20 working copy lines of the svn diff output."""
21 index_string = "Index: "
22 original_prefix = "--- "
23 working_prefix = "+++ "
24
25 def __init__(self, relpath):
26 # Note that we always use '/' as the path separator to be
27 # consistent with svn's cygwin-style output on Windows
28 self._relpath = relpath.replace("\\", "/")
29 self._current_file = ""
30 self._replacement_file = ""
31
32 def SetCurrentFile(self, file):
33 self._current_file = file
34 # Note that we always use '/' as the path separator to be
35 # consistent with svn's cygwin-style output on Windows
36 self._replacement_file = posixpath.join(self._relpath, file)
37
38 def ReplaceAndPrint(self, line):
39 print(line.replace(self._current_file, self._replacement_file))
40
41 def Filter(self, line):
42 if (line.startswith(self.index_string)):
43 self.SetCurrentFile(line[len(self.index_string):])
44 self.ReplaceAndPrint(line)
45 else:
46 if (line.startswith(self.original_prefix) or
47 line.startswith(self.working_prefix)):
48 self.ReplaceAndPrint(line)
49 else:
50 print line
51
52
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000053### SCM abstraction layer
54
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000055# Factory Method for SCM wrapper creation
56
57def CreateSCM(url=None, root_dir=None, relpath=None, scm_name='svn'):
58 # TODO(maruel): Deduce the SCM from the url.
59 scm_map = {
60 'svn' : SVNWrapper,
msb@chromium.orge28e4982009-09-25 20:51:45 +000061 'git' : GitWrapper,
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000062 }
msb@chromium.orge28e4982009-09-25 20:51:45 +000063
msb@chromium.org1b8779a2009-11-19 18:11:39 +000064 orig_url = url
65
66 if url:
67 url, _ = gclient_utils.SplitUrlRevision(url)
68 if url.startswith('git:') or url.startswith('ssh:') or url.endswith('.git'):
69 scm_name = 'git'
msb@chromium.orge28e4982009-09-25 20:51:45 +000070
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000071 if not scm_name in scm_map:
72 raise gclient_utils.Error('Unsupported scm %s' % scm_name)
msb@chromium.org1b8779a2009-11-19 18:11:39 +000073 return scm_map[scm_name](orig_url, root_dir, relpath, scm_name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000074
75
76# SCMWrapper base class
77
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000078class SCMWrapper(object):
79 """Add necessary glue between all the supported SCM.
80
81 This is the abstraction layer to bind to different SCM. Since currently only
82 subversion is supported, a lot of subersionism remains. This can be sorted out
83 once another SCM is supported."""
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +000084 def __init__(self, url=None, root_dir=None, relpath=None,
85 scm_name='svn'):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000086 self.scm_name = scm_name
87 self.url = url
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +000088 self._root_dir = root_dir
89 if self._root_dir:
90 self._root_dir = self._root_dir.replace('/', os.sep)
91 self.relpath = relpath
92 if self.relpath:
93 self.relpath = self.relpath.replace('/', os.sep)
msb@chromium.orge28e4982009-09-25 20:51:45 +000094 if self.relpath and self._root_dir:
95 self.checkout_path = os.path.join(self._root_dir, self.relpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000096
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000097 def RunCommand(self, command, options, args, file_list=None):
98 # file_list will have all files that are modified appended to it.
maruel@chromium.orgde754ac2009-09-17 18:04:50 +000099 if file_list is None:
100 file_list = []
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000101
msb@chromium.org0f282062009-11-06 20:14:02 +0000102 commands = ['cleanup', 'export', 'update', 'revert', 'revinfo',
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000103 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000104
105 if not command in commands:
106 raise gclient_utils.Error('Unknown command %s' % command)
107
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000108 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000109 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000110 command, self.scm_name))
111
112 return getattr(self, command)(options, args, file_list)
113
114
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000115class GitWrapper(SCMWrapper, scm.GIT):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000116 """Wrapper for Git"""
117
118 def cleanup(self, options, args, file_list):
119 """Cleanup working copy."""
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000120 __pychecker__ = 'unusednames=args,file_list,options'
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000121 self._Run(['prune'], redirect_stdout=False)
122 self._Run(['fsck'], redirect_stdout=False)
123 self._Run(['gc'], redirect_stdout=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000124
125 def diff(self, options, args, file_list):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000126 __pychecker__ = 'unusednames=args,file_list,options'
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000127 merge_base = self._Run(['merge-base', 'HEAD', 'origin'])
128 self._Run(['diff', merge_base], redirect_stdout=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000129
130 def export(self, options, args, file_list):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000131 __pychecker__ = 'unusednames=file_list,options'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000132 assert len(args) == 1
133 export_path = os.path.abspath(os.path.join(args[0], self.relpath))
134 if not os.path.exists(export_path):
135 os.makedirs(export_path)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000136 self._Run(['checkout-index', '-a', '--prefix=%s/' % export_path],
137 redirect_stdout=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000138
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000139 def pack(self, options, args, file_list):
140 """Generates a patch file which can be applied to the root of the
141 repository."""
142 __pychecker__ = 'unusednames=file_list,options'
143 path = os.path.join(self._root_dir, self.relpath)
144 merge_base = self._Run(['merge-base', 'HEAD', 'origin'])
145 command = ['diff', merge_base]
146 filterer = DiffFilterer(self.relpath)
147 self.RunAndFilterOutput(command, path, False, False, filterer.Filter)
148
msb@chromium.orge28e4982009-09-25 20:51:45 +0000149 def update(self, options, args, file_list):
150 """Runs git to update or transparently checkout the working copy.
151
152 All updated files will be appended to file_list.
153
154 Raises:
155 Error: if can't get URL for relative path.
156 """
157
158 if args:
159 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
160
msb@chromium.org83376f22009-12-11 22:25:31 +0000161 self._CheckMinVersion("1.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000162
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000163 url, revision = gclient_utils.SplitUrlRevision(self.url)
164 rev_str = ""
msb@chromium.orge28e4982009-09-25 20:51:45 +0000165 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000166 # Override the revision number.
167 revision = str(options.revision)
168 if revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000169 rev_str = ' at %s' % revision
msb@chromium.orge28e4982009-09-25 20:51:45 +0000170
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000171 if options.verbose:
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000172 print("\n_____ %s%s" % (self.relpath, rev_str))
173
msb@chromium.orge28e4982009-09-25 20:51:45 +0000174 if not os.path.exists(self.checkout_path):
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000175 self._Run(['clone', url, self.checkout_path],
176 cwd=self._root_dir, redirect_stdout=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000177 if revision:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000178 self._Run(['reset', '--hard', revision], redirect_stdout=False)
179 files = self._Run(['ls-files']).split()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000180 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
181 return
182
msb@chromium.orge28e4982009-09-25 20:51:45 +0000183 new_base = 'origin'
184 if revision:
185 new_base = revision
msb@chromium.org5bde4852009-12-14 16:47:12 +0000186 cur_branch = self._GetCurrentBranch()
187
188 # Check if we are in a rebase conflict
189 if cur_branch is None:
190 raise gclient_utils.Error('\n____ %s%s\n'
191 '\tAlready in a conflict, i.e. (no branch).\n'
192 '\tFix the conflict and run gclient again.\n'
193 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
194 '\tSee man git-rebase for details.\n'
195 % (self.relpath, rev_str))
196
msb@chromium.orgf2370632009-11-25 00:22:11 +0000197 merge_base = self._Run(['merge-base', 'HEAD', new_base])
198 self._Run(['remote', 'update'], redirect_stdout=False)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000199 files = self._Run(['diff', new_base, '--name-only']).split()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000200 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orgf2370632009-11-25 00:22:11 +0000201 self._Run(['rebase', '-v', '--onto', new_base, merge_base, cur_branch],
msb@chromium.org5bde4852009-12-14 16:47:12 +0000202 redirect_stdout=False, checkrc=False)
203
204 # If the rebase generated a conflict, abort and ask user to fix
205 if self._GetCurrentBranch() is None:
206 raise gclient_utils.Error('\n____ %s%s\n'
207 '\nConflict while rebasing this branch.\n'
208 'Fix the conflict and run gclient again.\n'
209 'See man git-rebase for details.\n'
210 % (self.relpath, rev_str))
211
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000212 print "Checked out revision %s." % self.revinfo(options, (), None)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000213
214 def revert(self, options, args, file_list):
215 """Reverts local modifications.
216
217 All reverted files will be appended to file_list.
218 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000219 __pychecker__ = 'unusednames=args'
msb@chromium.org260c6532009-10-28 03:22:35 +0000220 path = os.path.join(self._root_dir, self.relpath)
221 if not os.path.isdir(path):
222 # revert won't work if the directory doesn't exist. It needs to
223 # checkout instead.
224 print("\n_____ %s is missing, synching instead" % self.relpath)
225 # Don't reuse the args.
226 return self.update(options, [], file_list)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000227 merge_base = self._Run(['merge-base', 'HEAD', 'origin'])
228 files = self._Run(['diff', merge_base, '--name-only']).split()
229 self._Run(['reset', '--hard', merge_base], redirect_stdout=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000230 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
231
msb@chromium.org0f282062009-11-06 20:14:02 +0000232 def revinfo(self, options, args, file_list):
233 """Display revision"""
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000234 __pychecker__ = 'unusednames=args,file_list,options'
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000235 return self._Run(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000236
msb@chromium.orge28e4982009-09-25 20:51:45 +0000237 def runhooks(self, options, args, file_list):
238 self.status(options, args, file_list)
239
240 def status(self, options, args, file_list):
241 """Display status information."""
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000242 __pychecker__ = 'unusednames=args,options'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000243 if not os.path.isdir(self.checkout_path):
244 print('\n________ couldn\'t run status in %s:\nThe directory '
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000245 'does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000246 else:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000247 merge_base = self._Run(['merge-base', 'HEAD', 'origin'])
248 self._Run(['diff', '--name-status', merge_base], redirect_stdout=False)
249 files = self._Run(['diff', '--name-only', merge_base]).split()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000250 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
251
msb@chromium.org923a0372009-12-11 20:42:43 +0000252 def _CheckMinVersion(self, min_version):
msb@chromium.org83376f22009-12-11 22:25:31 +0000253 def only_int(val):
254 if val.isdigit():
255 return int(val)
256 else:
257 return 0
msb@chromium.orgba9b2392009-12-11 23:30:13 +0000258 version = self._Run(['--version'], cwd='.').split()[-1]
msb@chromium.org83376f22009-12-11 22:25:31 +0000259 version_list = map(only_int, version.split('.'))
msb@chromium.org923a0372009-12-11 20:42:43 +0000260 min_version_list = map(int, min_version.split('.'))
261 for min_ver in min_version_list:
262 ver = version_list.pop(0)
263 if min_ver > ver:
264 raise gclient_utils.Error('git version %s < minimum required %s' %
265 (version, min_version))
266 elif min_ver < ver:
267 return
268
msb@chromium.org5bde4852009-12-14 16:47:12 +0000269 def _GetCurrentBranch(self):
270 # Returns name of current branch
271 # Returns None if inside a (no branch)
272 tokens = self._Run(['branch']).split()
273 branch = tokens[tokens.index('*') + 1]
274 if branch == '(no':
275 return None
276 return branch
277
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000278 def _Run(self, args, cwd=None, checkrc=True, redirect_stdout=True):
279 # TODO(maruel): Merge with Capture?
maruel@chromium.orgffe96f02009-12-09 18:39:15 +0000280 if cwd is None:
281 cwd = self.checkout_path
msb@chromium.orge8e60e52009-11-02 21:50:56 +0000282 stdout=None
283 if redirect_stdout:
284 stdout=subprocess.PIPE
msb@chromium.orge28e4982009-09-25 20:51:45 +0000285 if cwd == None:
286 cwd = self.checkout_path
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000287 cmd = [self.COMMAND]
msb@chromium.orge28e4982009-09-25 20:51:45 +0000288 cmd.extend(args)
msb@chromium.orge8e60e52009-11-02 21:50:56 +0000289 sp = subprocess.Popen(cmd, cwd=cwd, stdout=stdout)
maruel@chromium.orgffe96f02009-12-09 18:39:15 +0000290 output = sp.communicate()[0]
msb@chromium.orge28e4982009-09-25 20:51:45 +0000291 if checkrc and sp.returncode:
292 raise gclient_utils.Error('git command %s returned %d' %
293 (args[0], sp.returncode))
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000294 if output is not None:
msb@chromium.orge8e60e52009-11-02 21:50:56 +0000295 return output.strip()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000296
297
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000298class SVNWrapper(SCMWrapper, scm.SVN):
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000299 """ Wrapper for SVN """
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000300
301 def cleanup(self, options, args, file_list):
302 """Cleanup working copy."""
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000303 __pychecker__ = 'unusednames=file_list,options'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000304 command = ['cleanup']
305 command.extend(args)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000306 self.Run(command, os.path.join(self._root_dir, self.relpath))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000307
308 def diff(self, options, args, file_list):
309 # NOTE: This function does not currently modify file_list.
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000310 __pychecker__ = 'unusednames=file_list,options'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000311 command = ['diff']
312 command.extend(args)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000313 self.Run(command, os.path.join(self._root_dir, self.relpath))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000314
315 def export(self, options, args, file_list):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000316 __pychecker__ = 'unusednames=file_list,options'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000317 assert len(args) == 1
318 export_path = os.path.abspath(os.path.join(args[0], self.relpath))
319 try:
320 os.makedirs(export_path)
321 except OSError:
322 pass
323 assert os.path.exists(export_path)
324 command = ['export', '--force', '.']
325 command.append(export_path)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000326 self.Run(command, os.path.join(self._root_dir, self.relpath))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000327
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000328 def pack(self, options, args, file_list):
329 """Generates a patch file which can be applied to the root of the
330 repository."""
331 __pychecker__ = 'unusednames=file_list,options'
332 path = os.path.join(self._root_dir, self.relpath)
333 command = ['diff']
334 command.extend(args)
335
336 filterer = DiffFilterer(self.relpath)
337 self.RunAndFilterOutput(command, path, False, False, filterer.Filter)
338
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000339 def update(self, options, args, file_list):
340 """Runs SCM to update or transparently checkout the working copy.
341
342 All updated files will be appended to file_list.
343
344 Raises:
345 Error: if can't get URL for relative path.
346 """
347 # Only update if git is not controlling the directory.
348 checkout_path = os.path.join(self._root_dir, self.relpath)
349 git_path = os.path.join(self._root_dir, self.relpath, '.git')
350 if os.path.exists(git_path):
351 print("________ found .git directory; skipping %s" % self.relpath)
352 return
353
354 if args:
355 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
356
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000357 url, revision = gclient_utils.SplitUrlRevision(self.url)
358 base_url = url
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000359 forced_revision = False
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000360 rev_str = ""
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000361 if options.revision:
362 # Override the revision number.
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000363 revision = str(options.revision)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000364 if revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000365 forced_revision = True
366 url = '%s@%s' % (url, revision)
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000367 rev_str = ' at %s' % revision
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000368
369 if not os.path.exists(checkout_path):
370 # We need to checkout.
371 command = ['checkout', url, checkout_path]
372 if revision:
373 command.extend(['--revision', str(revision)])
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000374 self.RunAndGetFileList(options, command, self._root_dir, file_list)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000375 return
376
377 # Get the existing scm url and the revision number of the current checkout.
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000378 from_info = self.CaptureInfo(os.path.join(checkout_path, '.'), '.')
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000379 if not from_info:
380 raise gclient_utils.Error("Can't update/checkout %r if an unversioned "
381 "directory is present. Delete the directory "
382 "and try again." %
383 checkout_path)
384
maruel@chromium.org7753d242009-10-07 17:40:24 +0000385 if options.manually_grab_svn_rev:
386 # Retrieve the current HEAD version because svn is slow at null updates.
387 if not revision:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000388 from_info_live = self.CaptureInfo(from_info['URL'], '.')
maruel@chromium.org7753d242009-10-07 17:40:24 +0000389 revision = str(from_info_live['Revision'])
390 rev_str = ' at %s' % revision
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000391
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000392 if from_info['URL'] != base_url:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000393 to_info = self.CaptureInfo(url, '.')
maruel@chromium.orge2ce0c72009-09-23 16:14:18 +0000394 if not to_info.get('Repository Root') or not to_info.get('UUID'):
395 # The url is invalid or the server is not accessible, it's safer to bail
396 # out right now.
397 raise gclient_utils.Error('This url is unreachable: %s' % url)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000398 can_switch = ((from_info['Repository Root'] != to_info['Repository Root'])
399 and (from_info['UUID'] == to_info['UUID']))
400 if can_switch:
401 print("\n_____ relocating %s to a new checkout" % self.relpath)
402 # We have different roots, so check if we can switch --relocate.
403 # Subversion only permits this if the repository UUIDs match.
404 # Perform the switch --relocate, then rewrite the from_url
405 # to reflect where we "are now." (This is the same way that
406 # Subversion itself handles the metadata when switch --relocate
407 # is used.) This makes the checks below for whether we
408 # can update to a revision or have to switch to a different
409 # branch work as expected.
410 # TODO(maruel): TEST ME !
411 command = ["switch", "--relocate",
412 from_info['Repository Root'],
413 to_info['Repository Root'],
414 self.relpath]
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000415 self.Run(command, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000416 from_info['URL'] = from_info['URL'].replace(
417 from_info['Repository Root'],
418 to_info['Repository Root'])
419 else:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000420 if self.CaptureStatus(checkout_path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000421 raise gclient_utils.Error("Can't switch the checkout to %s; UUID "
422 "don't match and there is local changes "
423 "in %s. Delete the directory and "
424 "try again." % (url, checkout_path))
425 # Ok delete it.
426 print("\n_____ switching %s to a new checkout" % self.relpath)
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +0000427 gclient_utils.RemoveDirectory(checkout_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000428 # We need to checkout.
429 command = ['checkout', url, checkout_path]
430 if revision:
431 command.extend(['--revision', str(revision)])
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000432 self.RunAndGetFileList(options, command, self._root_dir, file_list)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000433 return
434
435
436 # If the provided url has a revision number that matches the revision
437 # number of the existing directory, then we don't need to bother updating.
maruel@chromium.org2e0c6852009-09-24 00:02:07 +0000438 if not options.force and str(from_info['Revision']) == revision:
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000439 if options.verbose or not forced_revision:
440 print("\n_____ %s%s" % (self.relpath, rev_str))
441 return
442
443 command = ["update", checkout_path]
444 if revision:
445 command.extend(['--revision', str(revision)])
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000446 self.RunAndGetFileList(options, command, self._root_dir, file_list)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000447
448 def revert(self, options, args, file_list):
449 """Reverts local modifications. Subversion specific.
450
451 All reverted files will be appended to file_list, even if Subversion
452 doesn't know about them.
453 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000454 __pychecker__ = 'unusednames=args'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000455 path = os.path.join(self._root_dir, self.relpath)
456 if not os.path.isdir(path):
457 # svn revert won't work if the directory doesn't exist. It needs to
458 # checkout instead.
459 print("\n_____ %s is missing, synching instead" % self.relpath)
460 # Don't reuse the args.
461 return self.update(options, [], file_list)
462
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000463 for file_status in self.CaptureStatus(path):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000464 file_path = os.path.join(path, file_status[1])
465 if file_status[0][0] == 'X':
maruel@chromium.org754960e2009-09-21 12:31:05 +0000466 # Ignore externals.
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000467 logging.info('Ignoring external %s' % file_path)
maruel@chromium.org754960e2009-09-21 12:31:05 +0000468 continue
469
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000470 if logging.getLogger().isEnabledFor(logging.INFO):
471 logging.info('%s%s' % (file[0], file[1]))
472 else:
473 print(file_path)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000474 if file_status[0].isspace():
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000475 logging.error('No idea what is the status of %s.\n'
476 'You just found a bug in gclient, please ping '
477 'maruel@chromium.org ASAP!' % file_path)
478 # svn revert is really stupid. It fails on inconsistent line-endings,
479 # on switched directories, etc. So take no chance and delete everything!
480 try:
481 if not os.path.exists(file_path):
482 pass
483 elif os.path.isfile(file_path):
maruel@chromium.org754960e2009-09-21 12:31:05 +0000484 logging.info('os.remove(%s)' % file_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000485 os.remove(file_path)
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000486 elif os.path.isdir(file_path):
maruel@chromium.org754960e2009-09-21 12:31:05 +0000487 logging.info('gclient_utils.RemoveDirectory(%s)' % file_path)
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +0000488 gclient_utils.RemoveDirectory(file_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000489 else:
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000490 logging.error('no idea what is %s.\nYou just found a bug in gclient'
491 ', please ping maruel@chromium.org ASAP!' % file_path)
492 except EnvironmentError:
493 logging.error('Failed to remove %s.' % file_path)
494
maruel@chromium.org810a50b2009-10-05 23:03:18 +0000495 try:
496 # svn revert is so broken we don't even use it. Using
497 # "svn up --revision BASE" achieve the same effect.
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000498 self.RunAndGetFileList(options, ['update', '--revision', 'BASE'], path,
maruel@chromium.org810a50b2009-10-05 23:03:18 +0000499 file_list)
500 except OSError, e:
501 # Maybe the directory disapeared meanwhile. We don't want it to throw an
502 # exception.
503 logging.error('Failed to update:\n%s' % str(e))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000504
msb@chromium.org0f282062009-11-06 20:14:02 +0000505 def revinfo(self, options, args, file_list):
506 """Display revision"""
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000507 __pychecker__ = 'unusednames=args,file_list,options'
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000508 return self.CaptureHeadRevision(self.url)
msb@chromium.org0f282062009-11-06 20:14:02 +0000509
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000510 def runhooks(self, options, args, file_list):
511 self.status(options, args, file_list)
512
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000513 def status(self, options, args, file_list):
514 """Display status information."""
515 path = os.path.join(self._root_dir, self.relpath)
516 command = ['status']
517 command.extend(args)
518 if not os.path.isdir(path):
519 # svn status won't work if the directory doesn't exist.
520 print("\n________ couldn't run \'%s\' in \'%s\':\nThe directory "
521 "does not exist."
522 % (' '.join(command), path))
523 # There's no file list to retrieve.
524 else:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000525 self.RunAndGetFileList(options, command, path, file_list)