blob: b564ed48ea5affd1e8f12951260c98e7204f1b89 [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.orge6f78352010-01-13 17:05:33 +0000252 def FullUrlForRelativeUrl(self, url):
253 # Strip from last '/'
254 # Equivalent to unix basename
255 base_url = self.url
256 return base_url[:base_url.rfind('/')] + url
257
msb@chromium.org923a0372009-12-11 20:42:43 +0000258 def _CheckMinVersion(self, min_version):
msb@chromium.org83376f22009-12-11 22:25:31 +0000259 def only_int(val):
260 if val.isdigit():
261 return int(val)
262 else:
263 return 0
msb@chromium.orgba9b2392009-12-11 23:30:13 +0000264 version = self._Run(['--version'], cwd='.').split()[-1]
msb@chromium.org83376f22009-12-11 22:25:31 +0000265 version_list = map(only_int, version.split('.'))
msb@chromium.org923a0372009-12-11 20:42:43 +0000266 min_version_list = map(int, min_version.split('.'))
267 for min_ver in min_version_list:
268 ver = version_list.pop(0)
269 if min_ver > ver:
270 raise gclient_utils.Error('git version %s < minimum required %s' %
271 (version, min_version))
272 elif min_ver < ver:
273 return
274
msb@chromium.org5bde4852009-12-14 16:47:12 +0000275 def _GetCurrentBranch(self):
276 # Returns name of current branch
277 # Returns None if inside a (no branch)
278 tokens = self._Run(['branch']).split()
279 branch = tokens[tokens.index('*') + 1]
280 if branch == '(no':
281 return None
282 return branch
283
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000284 def _Run(self, args, cwd=None, checkrc=True, redirect_stdout=True):
285 # TODO(maruel): Merge with Capture?
maruel@chromium.orgffe96f02009-12-09 18:39:15 +0000286 if cwd is None:
287 cwd = self.checkout_path
msb@chromium.orge8e60e52009-11-02 21:50:56 +0000288 stdout=None
289 if redirect_stdout:
290 stdout=subprocess.PIPE
msb@chromium.orge28e4982009-09-25 20:51:45 +0000291 if cwd == None:
292 cwd = self.checkout_path
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000293 cmd = [self.COMMAND]
msb@chromium.orge28e4982009-09-25 20:51:45 +0000294 cmd.extend(args)
maruel@chromium.orgf3909bf2010-01-08 01:14:51 +0000295 logging.debug(cmd)
296 try:
297 sp = subprocess.Popen(cmd, cwd=cwd, stdout=stdout)
298 output = sp.communicate()[0]
299 except OSError:
300 raise gclient_utils.Error("git command '%s' failed to run." %
301 ' '.join(cmd) + "\nCheck that you have git installed.")
msb@chromium.orge28e4982009-09-25 20:51:45 +0000302 if checkrc and sp.returncode:
303 raise gclient_utils.Error('git command %s returned %d' %
304 (args[0], sp.returncode))
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000305 if output is not None:
msb@chromium.orge8e60e52009-11-02 21:50:56 +0000306 return output.strip()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000307
308
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000309class SVNWrapper(SCMWrapper, scm.SVN):
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000310 """ Wrapper for SVN """
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000311
312 def cleanup(self, options, args, file_list):
313 """Cleanup working copy."""
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000314 __pychecker__ = 'unusednames=file_list,options'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000315 command = ['cleanup']
316 command.extend(args)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000317 self.Run(command, os.path.join(self._root_dir, self.relpath))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000318
319 def diff(self, options, args, file_list):
320 # NOTE: This function does not currently modify 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 command = ['diff']
323 command.extend(args)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000324 self.Run(command, os.path.join(self._root_dir, self.relpath))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000325
326 def export(self, options, args, file_list):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000327 __pychecker__ = 'unusednames=file_list,options'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000328 assert len(args) == 1
329 export_path = os.path.abspath(os.path.join(args[0], self.relpath))
330 try:
331 os.makedirs(export_path)
332 except OSError:
333 pass
334 assert os.path.exists(export_path)
335 command = ['export', '--force', '.']
336 command.append(export_path)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000337 self.Run(command, os.path.join(self._root_dir, self.relpath))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000338
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000339 def pack(self, options, args, file_list):
340 """Generates a patch file which can be applied to the root of the
341 repository."""
342 __pychecker__ = 'unusednames=file_list,options'
343 path = os.path.join(self._root_dir, self.relpath)
344 command = ['diff']
345 command.extend(args)
346
347 filterer = DiffFilterer(self.relpath)
348 self.RunAndFilterOutput(command, path, False, False, filterer.Filter)
349
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000350 def update(self, options, args, file_list):
351 """Runs SCM to update or transparently checkout the working copy.
352
353 All updated files will be appended to file_list.
354
355 Raises:
356 Error: if can't get URL for relative path.
357 """
358 # Only update if git is not controlling the directory.
359 checkout_path = os.path.join(self._root_dir, self.relpath)
360 git_path = os.path.join(self._root_dir, self.relpath, '.git')
361 if os.path.exists(git_path):
362 print("________ found .git directory; skipping %s" % self.relpath)
363 return
364
365 if args:
366 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
367
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000368 url, revision = gclient_utils.SplitUrlRevision(self.url)
369 base_url = url
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000370 forced_revision = False
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000371 rev_str = ""
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000372 if options.revision:
373 # Override the revision number.
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000374 revision = str(options.revision)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000375 if revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000376 forced_revision = True
377 url = '%s@%s' % (url, revision)
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000378 rev_str = ' at %s' % revision
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000379
380 if not os.path.exists(checkout_path):
381 # We need to checkout.
382 command = ['checkout', url, checkout_path]
383 if revision:
384 command.extend(['--revision', str(revision)])
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000385 self.RunAndGetFileList(options, command, self._root_dir, file_list)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000386 return
387
388 # Get the existing scm url and the revision number of the current checkout.
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000389 from_info = self.CaptureInfo(os.path.join(checkout_path, '.'), '.')
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000390 if not from_info:
391 raise gclient_utils.Error("Can't update/checkout %r if an unversioned "
392 "directory is present. Delete the directory "
393 "and try again." %
394 checkout_path)
395
maruel@chromium.org7753d242009-10-07 17:40:24 +0000396 if options.manually_grab_svn_rev:
397 # Retrieve the current HEAD version because svn is slow at null updates.
398 if not revision:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000399 from_info_live = self.CaptureInfo(from_info['URL'], '.')
maruel@chromium.org7753d242009-10-07 17:40:24 +0000400 revision = str(from_info_live['Revision'])
401 rev_str = ' at %s' % revision
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000402
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000403 if from_info['URL'] != base_url:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000404 to_info = self.CaptureInfo(url, '.')
maruel@chromium.orge2ce0c72009-09-23 16:14:18 +0000405 if not to_info.get('Repository Root') or not to_info.get('UUID'):
406 # The url is invalid or the server is not accessible, it's safer to bail
407 # out right now.
408 raise gclient_utils.Error('This url is unreachable: %s' % url)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000409 can_switch = ((from_info['Repository Root'] != to_info['Repository Root'])
410 and (from_info['UUID'] == to_info['UUID']))
411 if can_switch:
412 print("\n_____ relocating %s to a new checkout" % self.relpath)
413 # We have different roots, so check if we can switch --relocate.
414 # Subversion only permits this if the repository UUIDs match.
415 # Perform the switch --relocate, then rewrite the from_url
416 # to reflect where we "are now." (This is the same way that
417 # Subversion itself handles the metadata when switch --relocate
418 # is used.) This makes the checks below for whether we
419 # can update to a revision or have to switch to a different
420 # branch work as expected.
421 # TODO(maruel): TEST ME !
422 command = ["switch", "--relocate",
423 from_info['Repository Root'],
424 to_info['Repository Root'],
425 self.relpath]
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000426 self.Run(command, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000427 from_info['URL'] = from_info['URL'].replace(
428 from_info['Repository Root'],
429 to_info['Repository Root'])
430 else:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000431 if self.CaptureStatus(checkout_path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000432 raise gclient_utils.Error("Can't switch the checkout to %s; UUID "
433 "don't match and there is local changes "
434 "in %s. Delete the directory and "
435 "try again." % (url, checkout_path))
436 # Ok delete it.
437 print("\n_____ switching %s to a new checkout" % self.relpath)
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +0000438 gclient_utils.RemoveDirectory(checkout_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000439 # We need to checkout.
440 command = ['checkout', url, checkout_path]
441 if revision:
442 command.extend(['--revision', str(revision)])
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000443 self.RunAndGetFileList(options, command, self._root_dir, file_list)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000444 return
445
446
447 # If the provided url has a revision number that matches the revision
448 # number of the existing directory, then we don't need to bother updating.
maruel@chromium.org2e0c6852009-09-24 00:02:07 +0000449 if not options.force and str(from_info['Revision']) == revision:
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000450 if options.verbose or not forced_revision:
451 print("\n_____ %s%s" % (self.relpath, rev_str))
452 return
453
454 command = ["update", checkout_path]
455 if revision:
456 command.extend(['--revision', str(revision)])
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000457 self.RunAndGetFileList(options, command, self._root_dir, file_list)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000458
459 def revert(self, options, args, file_list):
460 """Reverts local modifications. Subversion specific.
461
462 All reverted files will be appended to file_list, even if Subversion
463 doesn't know about them.
464 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000465 __pychecker__ = 'unusednames=args'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000466 path = os.path.join(self._root_dir, self.relpath)
467 if not os.path.isdir(path):
468 # svn revert won't work if the directory doesn't exist. It needs to
469 # checkout instead.
470 print("\n_____ %s is missing, synching instead" % self.relpath)
471 # Don't reuse the args.
472 return self.update(options, [], file_list)
473
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000474 for file_status in self.CaptureStatus(path):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000475 file_path = os.path.join(path, file_status[1])
476 if file_status[0][0] == 'X':
maruel@chromium.org754960e2009-09-21 12:31:05 +0000477 # Ignore externals.
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000478 logging.info('Ignoring external %s' % file_path)
maruel@chromium.org754960e2009-09-21 12:31:05 +0000479 continue
480
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000481 if logging.getLogger().isEnabledFor(logging.INFO):
482 logging.info('%s%s' % (file[0], file[1]))
483 else:
484 print(file_path)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000485 if file_status[0].isspace():
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000486 logging.error('No idea what is the status of %s.\n'
487 'You just found a bug in gclient, please ping '
488 'maruel@chromium.org ASAP!' % file_path)
489 # svn revert is really stupid. It fails on inconsistent line-endings,
490 # on switched directories, etc. So take no chance and delete everything!
491 try:
492 if not os.path.exists(file_path):
493 pass
maruel@chromium.orgd2e78ff2010-01-11 20:37:19 +0000494 elif os.path.isfile(file_path) or os.path.islink(file_path):
maruel@chromium.org754960e2009-09-21 12:31:05 +0000495 logging.info('os.remove(%s)' % file_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000496 os.remove(file_path)
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000497 elif os.path.isdir(file_path):
maruel@chromium.org754960e2009-09-21 12:31:05 +0000498 logging.info('gclient_utils.RemoveDirectory(%s)' % file_path)
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +0000499 gclient_utils.RemoveDirectory(file_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000500 else:
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +0000501 logging.error('no idea what is %s.\nYou just found a bug in gclient'
502 ', please ping maruel@chromium.org ASAP!' % file_path)
503 except EnvironmentError:
504 logging.error('Failed to remove %s.' % file_path)
505
maruel@chromium.org810a50b2009-10-05 23:03:18 +0000506 try:
507 # svn revert is so broken we don't even use it. Using
508 # "svn up --revision BASE" achieve the same effect.
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000509 self.RunAndGetFileList(options, ['update', '--revision', 'BASE'], path,
maruel@chromium.org810a50b2009-10-05 23:03:18 +0000510 file_list)
511 except OSError, e:
512 # Maybe the directory disapeared meanwhile. We don't want it to throw an
513 # exception.
514 logging.error('Failed to update:\n%s' % str(e))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000515
msb@chromium.org0f282062009-11-06 20:14:02 +0000516 def revinfo(self, options, args, file_list):
517 """Display revision"""
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000518 __pychecker__ = 'unusednames=args,file_list,options'
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000519 return self.CaptureHeadRevision(self.url)
msb@chromium.org0f282062009-11-06 20:14:02 +0000520
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000521 def runhooks(self, options, args, file_list):
522 self.status(options, args, file_list)
523
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000524 def status(self, options, args, file_list):
525 """Display status information."""
526 path = os.path.join(self._root_dir, self.relpath)
527 command = ['status']
528 command.extend(args)
529 if not os.path.isdir(path):
530 # svn status won't work if the directory doesn't exist.
531 print("\n________ couldn't run \'%s\' in \'%s\':\nThe directory "
532 "does not exist."
533 % (' '.join(command), path))
534 # There's no file list to retrieve.
535 else:
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000536 self.RunAndGetFileList(options, command, path, file_list)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000537
538 def FullUrlForRelativeUrl(self, url):
539 # Find the forth '/' and strip from there. A bit hackish.
540 return '/'.join(self.url.split('/')[:4]) + url