blob: 93f69974b83db0e4a10c0e9340c5ca2e4679734d [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)
maruel@chromium.orgf3909bf2010-01-08 01:14:51 +0000289 logging.debug(cmd)
290 try:
291 sp = subprocess.Popen(cmd, cwd=cwd, stdout=stdout)
292 output = sp.communicate()[0]
293 except OSError:
294 raise gclient_utils.Error("git command '%s' failed to run." %
295 ' '.join(cmd) + "\nCheck that you have git installed.")
msb@chromium.orge28e4982009-09-25 20:51:45 +0000296 if checkrc and sp.returncode:
297 raise gclient_utils.Error('git command %s returned %d' %
298 (args[0], sp.returncode))
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000299 if output is not None:
msb@chromium.orge8e60e52009-11-02 21:50:56 +0000300 return output.strip()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000301
302
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000303class SVNWrapper(SCMWrapper, scm.SVN):
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000304 """ Wrapper for SVN """
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000305
306 def cleanup(self, options, args, file_list):
307 """Cleanup working copy."""
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000308 __pychecker__ = 'unusednames=file_list,options'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000309 command = ['cleanup']
310 command.extend(args)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000311 self.Run(command, os.path.join(self._root_dir, self.relpath))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000312
313 def diff(self, options, args, file_list):
314 # NOTE: This function does not currently modify file_list.
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000315 __pychecker__ = 'unusednames=file_list,options'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000316 command = ['diff']
317 command.extend(args)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000318 self.Run(command, os.path.join(self._root_dir, self.relpath))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000319
320 def export(self, options, args, file_list):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000321 __pychecker__ = 'unusednames=file_list,options'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000322 assert len(args) == 1
323 export_path = os.path.abspath(os.path.join(args[0], self.relpath))
324 try:
325 os.makedirs(export_path)
326 except OSError:
327 pass
328 assert os.path.exists(export_path)
329 command = ['export', '--force', '.']
330 command.append(export_path)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000331 self.Run(command, os.path.join(self._root_dir, self.relpath))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000332
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000333 def pack(self, options, args, file_list):
334 """Generates a patch file which can be applied to the root of the
335 repository."""
336 __pychecker__ = 'unusednames=file_list,options'
337 path = os.path.join(self._root_dir, self.relpath)
338 command = ['diff']
339 command.extend(args)
340
341 filterer = DiffFilterer(self.relpath)
342 self.RunAndFilterOutput(command, path, False, False, filterer.Filter)
343
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000344 def update(self, options, args, file_list):
345 """Runs SCM to update or transparently checkout the working copy.
346
347 All updated files will be appended to file_list.
348
349 Raises:
350 Error: if can't get URL for relative path.
351 """
352 # Only update if git is not controlling the directory.
353 checkout_path = os.path.join(self._root_dir, self.relpath)
354 git_path = os.path.join(self._root_dir, self.relpath, '.git')
355 if os.path.exists(git_path):
356 print("________ found .git directory; skipping %s" % self.relpath)
357 return
358
359 if args:
360 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
361
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000362 url, revision = gclient_utils.SplitUrlRevision(self.url)
363 base_url = url
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000364 forced_revision = False
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000365 rev_str = ""
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000366 if options.revision:
367 # Override the revision number.
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000368 revision = str(options.revision)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000369 if revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000370 forced_revision = True
371 url = '%s@%s' % (url, revision)
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000372 rev_str = ' at %s' % revision
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000373
374 if not os.path.exists(checkout_path):
375 # We need to checkout.
376 command = ['checkout', url, checkout_path]
377 if revision:
378 command.extend(['--revision', str(revision)])
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000379 self.RunAndGetFileList(options, command, self._root_dir, file_list)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000380 return
381
382 # Get the existing scm url and the revision number of the current checkout.
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000383 from_info = self.CaptureInfo(os.path.join(checkout_path, '.'), '.')
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000384 if not from_info:
385 raise gclient_utils.Error("Can't update/checkout %r if an unversioned "
386 "directory is present. Delete the directory "
387 "and try again." %
388 checkout_path)
389
maruel@chromium.org7753d242009-10-07 17:40:24 +0000390 if options.manually_grab_svn_rev:
391 # Retrieve the current HEAD version because svn is slow at null updates.
392 if not revision:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000393 from_info_live = self.CaptureInfo(from_info['URL'], '.')
maruel@chromium.org7753d242009-10-07 17:40:24 +0000394 revision = str(from_info_live['Revision'])
395 rev_str = ' at %s' % revision
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000396
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000397 if from_info['URL'] != base_url:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000398 to_info = self.CaptureInfo(url, '.')
maruel@chromium.orge2ce0c72009-09-23 16:14:18 +0000399 if not to_info.get('Repository Root') or not to_info.get('UUID'):
400 # The url is invalid or the server is not accessible, it's safer to bail
401 # out right now.
402 raise gclient_utils.Error('This url is unreachable: %s' % url)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000403 can_switch = ((from_info['Repository Root'] != to_info['Repository Root'])
404 and (from_info['UUID'] == to_info['UUID']))
405 if can_switch:
406 print("\n_____ relocating %s to a new checkout" % self.relpath)
407 # We have different roots, so check if we can switch --relocate.
408 # Subversion only permits this if the repository UUIDs match.
409 # Perform the switch --relocate, then rewrite the from_url
410 # to reflect where we "are now." (This is the same way that
411 # Subversion itself handles the metadata when switch --relocate
412 # is used.) This makes the checks below for whether we
413 # can update to a revision or have to switch to a different
414 # branch work as expected.
415 # TODO(maruel): TEST ME !
416 command = ["switch", "--relocate",
417 from_info['Repository Root'],
418 to_info['Repository Root'],
419 self.relpath]
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000420 self.Run(command, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000421 from_info['URL'] = from_info['URL'].replace(
422 from_info['Repository Root'],
423 to_info['Repository Root'])
424 else:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000425 if self.CaptureStatus(checkout_path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000426 raise gclient_utils.Error("Can't switch the checkout to %s; UUID "
427 "don't match and there is local changes "
428 "in %s. Delete the directory and "
429 "try again." % (url, checkout_path))
430 # Ok delete it.
431 print("\n_____ switching %s to a new checkout" % self.relpath)
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +0000432 gclient_utils.RemoveDirectory(checkout_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000433 # We need to checkout.
434 command = ['checkout', url, checkout_path]
435 if revision:
436 command.extend(['--revision', str(revision)])
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000437 self.RunAndGetFileList(options, command, self._root_dir, file_list)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000438 return
439
440
441 # If the provided url has a revision number that matches the revision
442 # number of the existing directory, then we don't need to bother updating.
maruel@chromium.org2e0c6852009-09-24 00:02:07 +0000443 if not options.force and str(from_info['Revision']) == revision:
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000444 if options.verbose or not forced_revision:
445 print("\n_____ %s%s" % (self.relpath, rev_str))
446 return
447
448 command = ["update", checkout_path]
449 if revision:
450 command.extend(['--revision', str(revision)])
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000451 self.RunAndGetFileList(options, command, self._root_dir, file_list)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000452
453 def revert(self, options, args, file_list):
454 """Reverts local modifications. Subversion specific.
455
456 All reverted files will be appended to file_list, even if Subversion
457 doesn't know about them.
458 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000459 __pychecker__ = 'unusednames=args'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000460 path = os.path.join(self._root_dir, self.relpath)
461 if not os.path.isdir(path):
462 # svn revert won't work if the directory doesn't exist. It needs to
463 # checkout instead.
464 print("\n_____ %s is missing, synching instead" % self.relpath)
465 # Don't reuse the args.
466 return self.update(options, [], file_list)
467
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000468 for file_status in self.CaptureStatus(path):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000469 file_path = os.path.join(path, file_status[1])
470 if file_status[0][0] == 'X':
maruel@chromium.org754960e2009-09-21 12:31:05 +0000471 # Ignore externals.
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000472 logging.info('Ignoring external %s' % file_path)
maruel@chromium.org754960e2009-09-21 12:31:05 +0000473 continue
474
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000475 if logging.getLogger().isEnabledFor(logging.INFO):
476 logging.info('%s%s' % (file[0], file[1]))
477 else:
478 print(file_path)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000479 if file_status[0].isspace():
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000480 logging.error('No idea what is the status of %s.\n'
481 'You just found a bug in gclient, please ping '
482 'maruel@chromium.org ASAP!' % file_path)
483 # svn revert is really stupid. It fails on inconsistent line-endings,
484 # on switched directories, etc. So take no chance and delete everything!
485 try:
486 if not os.path.exists(file_path):
487 pass
488 elif os.path.isfile(file_path):
maruel@chromium.org754960e2009-09-21 12:31:05 +0000489 logging.info('os.remove(%s)' % file_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000490 os.remove(file_path)
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000491 elif os.path.isdir(file_path):
maruel@chromium.org754960e2009-09-21 12:31:05 +0000492 logging.info('gclient_utils.RemoveDirectory(%s)' % file_path)
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +0000493 gclient_utils.RemoveDirectory(file_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000494 else:
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000495 logging.error('no idea what is %s.\nYou just found a bug in gclient'
496 ', please ping maruel@chromium.org ASAP!' % file_path)
497 except EnvironmentError:
498 logging.error('Failed to remove %s.' % file_path)
499
maruel@chromium.org810a50b2009-10-05 23:03:18 +0000500 try:
501 # svn revert is so broken we don't even use it. Using
502 # "svn up --revision BASE" achieve the same effect.
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000503 self.RunAndGetFileList(options, ['update', '--revision', 'BASE'], path,
maruel@chromium.org810a50b2009-10-05 23:03:18 +0000504 file_list)
505 except OSError, e:
506 # Maybe the directory disapeared meanwhile. We don't want it to throw an
507 # exception.
508 logging.error('Failed to update:\n%s' % str(e))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000509
msb@chromium.org0f282062009-11-06 20:14:02 +0000510 def revinfo(self, options, args, file_list):
511 """Display revision"""
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000512 __pychecker__ = 'unusednames=args,file_list,options'
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000513 return self.CaptureHeadRevision(self.url)
msb@chromium.org0f282062009-11-06 20:14:02 +0000514
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000515 def runhooks(self, options, args, file_list):
516 self.status(options, args, file_list)
517
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000518 def status(self, options, args, file_list):
519 """Display status information."""
520 path = os.path.join(self._root_dir, self.relpath)
521 command = ['status']
522 command.extend(args)
523 if not os.path.isdir(path):
524 # svn status won't work if the directory doesn't exist.
525 print("\n________ couldn't run \'%s\' in \'%s\':\nThe directory "
526 "does not exist."
527 % (' '.join(command), path))
528 # There's no file list to retrieve.
529 else:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000530 self.RunAndGetFileList(options, command, path, file_list)