blob: 5947c99f982ef33ce8ea9a36bf77b0cb460bcba3 [file] [log] [blame]
steveblock@chromium.org93567042012-02-15 01:02:26 +00001# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +00002# 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
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00007from __future__ import print_function
8
dnj@chromium.orgf2707a62014-07-07 17:08:48 +00009import collections
borenet@google.comb2256212014-05-07 20:57:28 +000010import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000011import logging
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000012import os
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000013import posixpath
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000014import re
borenet@google.com88d10082014-03-21 17:24:48 +000015import shlex
maruel@chromium.org90541732011-04-01 17:54:18 +000016import sys
ilevy@chromium.org3534aa52013-07-20 01:58:08 +000017import tempfile
zty@chromium.org6279e8a2014-02-13 01:45:25 +000018import traceback
hinoka@google.com2f2ca142014-01-07 03:59:18 +000019import urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000020
hinoka@google.com2f2ca142014-01-07 03:59:18 +000021import download_from_google_storage
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000022import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +000023import git_cache
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000024import scm
borenet@google.comb2256212014-05-07 20:57:28 +000025import shutil
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000026import subprocess2
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000027
28
szager@chromium.org71cbb502013-04-19 23:30:15 +000029THIS_FILE_PATH = os.path.abspath(__file__)
30
hinoka@google.com2f2ca142014-01-07 03:59:18 +000031GSUTIL_DEFAULT_PATH = os.path.join(
32 os.path.dirname(os.path.abspath(__file__)),
33 'third_party', 'gsutil', 'gsutil')
34
szager@chromium.org848fd492014-04-09 19:06:44 +000035CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000036class DiffFiltererWrapper(object):
37 """Simple base class which tracks which file is being diffed and
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000038 replaces instances of its file name in the original and
msb@chromium.orgd6504212010-01-13 17:34:31 +000039 working copy lines of the svn/git diff output."""
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000040 index_string = None
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000041 original_prefix = "--- "
42 working_prefix = "+++ "
43
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000044 def __init__(self, relpath, print_func):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000045 # Note that we always use '/' as the path separator to be
46 # consistent with svn's cygwin-style output on Windows
47 self._relpath = relpath.replace("\\", "/")
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000048 self._current_file = None
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000049 self._print_func = print_func
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000050
maruel@chromium.org6e29d572010-06-04 17:32:20 +000051 def SetCurrentFile(self, current_file):
52 self._current_file = current_file
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000053
iannucci@chromium.org3830a672013-02-19 20:15:14 +000054 @property
55 def _replacement_file(self):
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000056 return posixpath.join(self._relpath, self._current_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000057
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000058 def _Replace(self, line):
59 return line.replace(self._current_file, self._replacement_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000060
61 def Filter(self, line):
62 if (line.startswith(self.index_string)):
63 self.SetCurrentFile(line[len(self.index_string):])
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000064 line = self._Replace(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000065 else:
66 if (line.startswith(self.original_prefix) or
67 line.startswith(self.working_prefix)):
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000068 line = self._Replace(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000069 self._print_func(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000070
71
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000072class SvnDiffFilterer(DiffFiltererWrapper):
73 index_string = "Index: "
74
75
76class GitDiffFilterer(DiffFiltererWrapper):
77 index_string = "diff --git "
78
79 def SetCurrentFile(self, current_file):
80 # Get filename by parsing "a/<filename> b/<filename>"
81 self._current_file = current_file[:(len(current_file)/2)][2:]
82
83 def _Replace(self, line):
84 return re.sub("[a|b]/" + self._current_file, self._replacement_file, line)
85
86
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000087### SCM abstraction layer
88
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000089# Factory Method for SCM wrapper creation
90
maruel@chromium.org9eda4112010-06-11 18:56:10 +000091def GetScmName(url):
92 if url:
93 url, _ = gclient_utils.SplitUrlRevision(url)
94 if (url.startswith('git://') or url.startswith('ssh://') or
igorgatis@gmail.com4e075672011-11-21 16:35:08 +000095 url.startswith('git+http://') or url.startswith('git+https://') or
borenet@google.coma2aec972014-04-22 21:35:15 +000096 url.endswith('.git') or url.startswith('sso://') or
97 'googlesource' in url):
maruel@chromium.org9eda4112010-06-11 18:56:10 +000098 return 'git'
maruel@chromium.orgb74dca22010-06-11 20:10:40 +000099 elif (url.startswith('http://') or url.startswith('https://') or
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000100 url.startswith('svn://') or url.startswith('svn+ssh://')):
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000101 return 'svn'
102 return None
103
104
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000105def CreateSCM(url, root_dir=None, relpath=None, out_fh=None, out_cb=None):
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000106 SCM_MAP = {
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000107 'svn' : SVNWrapper,
msb@chromium.orge28e4982009-09-25 20:51:45 +0000108 'git' : GitWrapper,
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000109 }
msb@chromium.orge28e4982009-09-25 20:51:45 +0000110
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000111 scm_name = GetScmName(url)
112 if not scm_name in SCM_MAP:
113 raise gclient_utils.Error('No SCM found for url %s' % url)
mukai@chromium.org9e3e82c2012-04-18 12:55:43 +0000114 scm_class = SCM_MAP[scm_name]
115 if not scm_class.BinaryExists():
116 raise gclient_utils.Error('%s command not found' % scm_name)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000117 return scm_class(url, root_dir, relpath, out_fh, out_cb)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000118
119
120# SCMWrapper base class
121
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000122class SCMWrapper(object):
123 """Add necessary glue between all the supported SCM.
124
msb@chromium.orgd6504212010-01-13 17:34:31 +0000125 This is the abstraction layer to bind to different SCM.
126 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000127
128 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
129 out_cb=None):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000130 self.url = url
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +0000131 self._root_dir = root_dir
132 if self._root_dir:
133 self._root_dir = self._root_dir.replace('/', os.sep)
134 self.relpath = relpath
135 if self.relpath:
136 self.relpath = self.relpath.replace('/', os.sep)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000137 if self.relpath and self._root_dir:
138 self.checkout_path = os.path.join(self._root_dir, self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000139 if out_fh is None:
140 out_fh = sys.stdout
141 self.out_fh = out_fh
142 self.out_cb = out_cb
143
144 def Print(self, *args, **kwargs):
145 kwargs.setdefault('file', self.out_fh)
146 if kwargs.pop('timestamp', True):
147 self.out_fh.write('[%s] ' % gclient_utils.Elapsed())
148 print(*args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000149
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000150 def RunCommand(self, command, options, args, file_list=None):
phajdan.jr@chromium.org6e043f72011-05-02 07:24:32 +0000151 commands = ['cleanup', 'update', 'updatesingle', 'revert',
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000152 'revinfo', 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000153
154 if not command in commands:
155 raise gclient_utils.Error('Unknown command %s' % command)
156
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000157 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000158 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000159 command, self.__class__.__name__))
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000160
161 return getattr(self, command)(options, args, file_list)
162
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000163 def GetActualRemoteURL(self):
borenet@google.com88d10082014-03-21 17:24:48 +0000164 """Attempt to determine the remote URL for this SCMWrapper."""
borenet@google.combda475e2014-03-24 19:04:45 +0000165 if os.path.exists(os.path.join(self.checkout_path, '.git')):
szager@chromium.orgce2fccb2014-05-02 00:57:10 +0000166 actual_remote_url = shlex.split(scm.GIT.Capture(
borenet@google.com88d10082014-03-21 17:24:48 +0000167 ['config', '--local', '--get-regexp', r'remote.*.url'],
borenet@google.comc3e09d22014-04-10 13:58:18 +0000168 cwd=self.checkout_path))[1]
borenet@google.com4e9be262014-04-08 19:40:30 +0000169
170 # If a cache_dir is used, obtain the actual remote URL from the cache.
171 if getattr(self, 'cache_dir', None):
borenet@google.com46d09f62014-04-10 18:50:23 +0000172 url, _ = gclient_utils.SplitUrlRevision(self.url)
173 mirror = git_cache.Mirror(url)
szager@chromium.org848fd492014-04-09 19:06:44 +0000174 if (mirror.exists() and mirror.mirror_path.replace('\\', '/') ==
borenet@google.com4e9be262014-04-08 19:40:30 +0000175 actual_remote_url.replace('\\', '/')):
szager@chromium.orgce2fccb2014-05-02 00:57:10 +0000176 actual_remote_url = shlex.split(scm.GIT.Capture(
borenet@google.com4e9be262014-04-08 19:40:30 +0000177 ['config', '--local', '--get-regexp', r'remote.*.url'],
szager@chromium.org848fd492014-04-09 19:06:44 +0000178 cwd=mirror.mirror_path))[1]
borenet@google.com4e9be262014-04-08 19:40:30 +0000179 return actual_remote_url
180
181 # Svn
borenet@google.combda475e2014-03-24 19:04:45 +0000182 if os.path.exists(os.path.join(self.checkout_path, '.svn')):
borenet@google.com88d10082014-03-21 17:24:48 +0000183 return scm.SVN.CaptureLocalInfo([], self.checkout_path)['URL']
borenet@google.com88d10082014-03-21 17:24:48 +0000184 return None
185
borenet@google.com4e9be262014-04-08 19:40:30 +0000186 def DoesRemoteURLMatch(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000187 """Determine whether the remote URL of this checkout is the expected URL."""
188 if not os.path.exists(self.checkout_path):
189 # A checkout which doesn't exist can't be broken.
190 return True
191
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000192 actual_remote_url = self.GetActualRemoteURL()
borenet@google.com88d10082014-03-21 17:24:48 +0000193 if actual_remote_url:
borenet@google.com8156c9f2014-04-01 16:41:36 +0000194 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
195 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
borenet@google.com88d10082014-03-21 17:24:48 +0000196 else:
197 # This may occur if the self.checkout_path exists but does not contain a
198 # valid git or svn checkout.
199 return False
200
borenet@google.comb09097a2014-04-09 19:09:08 +0000201 def _DeleteOrMove(self, force):
202 """Delete the checkout directory or move it out of the way.
203
204 Args:
205 force: bool; if True, delete the directory. Otherwise, just move it.
206 """
borenet@google.comb2256212014-05-07 20:57:28 +0000207 if force and os.environ.get('CHROME_HEADLESS') == '1':
208 self.Print('_____ Conflicting directory found in %s. Removing.'
209 % self.checkout_path)
210 gclient_utils.AddWarning('Conflicting directory %s deleted.'
211 % self.checkout_path)
212 gclient_utils.rmtree(self.checkout_path)
213 else:
214 bad_scm_dir = os.path.join(self._root_dir, '_bad_scm',
215 os.path.dirname(self.relpath))
216
217 try:
218 os.makedirs(bad_scm_dir)
219 except OSError as e:
220 if e.errno != errno.EEXIST:
221 raise
222
223 dest_path = tempfile.mkdtemp(
224 prefix=os.path.basename(self.relpath),
225 dir=bad_scm_dir)
226 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
227 % (self.checkout_path, dest_path))
228 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
229 % (self.checkout_path, dest_path))
230 shutil.move(self.checkout_path, dest_path)
borenet@google.comb09097a2014-04-09 19:09:08 +0000231
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000232
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000233_GitRefishBase = collections.namedtuple('GitRef', (
234 # The initial string that the parsed Refish came from
235 'source',
236 # Is this ref a branch?
237 'is_branch',
238 # The name of this ref when accessed through the local repository
239 'local_ref',
240 # The name of the remote that this refish resides on
241 'remote',
242 # The path of this refish on the remote (e.g., 'master')
243 'remote_ref',
244 # The remote-qualified path to this refish (e.g., 'origin/master')
245 'remote_refspec',
246 # If a branch, the expected name of the upstream branch that it should
247 # track; otherwise, 'None'
248 'upstream_branch',
249))
250
251
252class GitRefish(_GitRefishBase):
253 # Disable 'no __init__' warning | pylint: disable=W0232
254 """Implements refish parsing and properties.
255
256 This class is designed to concentrate and codify the assumptions made by
257 'gclient' code about refs and revisions.
258 """
259
260 _DEFAULT_REMOTE = 'origin'
261
262 _GIT_SHA1_RE = re.compile('[0-9a-f]{40}', re.IGNORECASE)
263 _GIT_SHA1_SHORT_RE = re.compile('[0-9a-f]{4,40}', re.IGNORECASE)
264
265 def __str__(self):
266 return self.source
267
268 @classmethod
269 def Parse(cls, ref, remote=None, other_remotes=None):
270 """Parses a ref into a 'GitRefish' instance.
271
272 Args:
273 ref: (str) The refish (branch, tag, hash, etc.) to parse
274 remote: (None/str) If supplied, the name of the primary remote. In
275 addtion to being recognized as a remote string, the primary remote
276 will also be used (as needed) when generating the remote refspec. If
277 omitted, the default remote name ('origin') will be used.
278 other_remotes: (None/iterable) If supplied, a list of strings to
279 recognize as remotes in addition to 'remote'.
280 """
281 assert ref == ref.strip()
282
283 # Use default set of remotes
284 remote = remote or cls._DEFAULT_REMOTE
285 remotes = {remote}
286 if other_remotes:
287 remotes.update(other_remotes)
288
289 # Treat 'ref' as a '/'-delimited set of items; analyze their contents
290 ref_split = local_ref = remote_ref = tuple(ref.split('/'))
291 is_branch = True
292 if len(ref_split) > 1:
293 if ref_split[0] == 'refs':
294 if len(ref_split) >= 3 and ref_split[1] == 'heads':
295 # refs/heads/foo/bar
296 #
297 # Local Ref: foo/bar
298 # Remote Ref: foo/bar
299 local_ref = remote_ref = ref_split[2:]
300 elif len(ref_split) >= 4 and ref_split[1] == 'remotes':
301 # refs/remotes/<REMOTE>/foo/bar
302 # This is a bad assumption, and this logic can be improved, but it
303 # is consistent with traditional 'gclient' logic.
304 #
305 # Local Ref: refs/remotes/<REMOTE>/foo/bar
306 # Remote: <REMOTE>
307 # Remote Ref: foo/bar
308 remote = ref_split[2]
309 remote_ref = ref_split[3:]
310 elif len(ref_split) >= 2 and ref_split[0] in remotes:
311 # origin/master
312 #
313 # Local Ref: refs/remotes/origin/master
314 # Remote Ref: master
315 remote = ref_split[0]
316 remote_ref = ref_split[1:]
317 local_ref = ('refs', 'remotes') + ref_split
318
319 # (Default) The refish has multiple paths. Assume at least that it's a
320 # branch name.
321 #
322 # foo/bar
323 # refs/foo/bar
324 #
325 # Local Ref: foo/bar
326 # Remote ref: foo/bar
327 else:
328 # It could be a hash, a short-hash, or a tag
329 is_branch = False
330
331 # Determine how the ref should be referenced remotely
332 if is_branch:
333 # foo/bar/baz => origin/foo/bar/baz
334 remote_refspec = (remote,) + remote_ref
335 else:
336 # Refer to the hash/tag directly. This is actually not allowed in
337 # 'fetch', although it oftentimes works regardless.
338 #
339 # <HASH/TAG> => <HASH/TAG>
340 remote_refspec = (ref,)
341
342 # Calculate the upstream branch
343 if is_branch:
344 # Convert '/refs/heads/...' to 'refs/remotes/REMOTE/...'
345 if ref_split[:2] == ('refs', 'heads'):
346 upstream_branch = ('refs', 'remotes', remote) + ref_split[2:]
347 else:
348 upstream_branch = ref_split
349 else:
350 upstream_branch = None
351
352 def compose(ref_tuple):
353 return '/'.join(ref_tuple) if ref_tuple else ref_tuple
354
355 return cls(
356 source=ref,
357 is_branch=is_branch,
358 local_ref=compose(local_ref),
359 remote=remote,
360 remote_ref=compose(remote_ref),
361 remote_refspec=compose(remote_refspec),
362 upstream_branch=compose(upstream_branch),
363 )
364
365
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000366class GitWrapper(SCMWrapper):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000367 """Wrapper for Git"""
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000368 name = 'git'
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000369 remote = 'origin'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000370
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000371 cache_dir = None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000372
szager@chromium.org848fd492014-04-09 19:06:44 +0000373 def __init__(self, url=None, *args):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000374 """Removes 'git+' fake prefix from git URL."""
375 if url.startswith('git+http://') or url.startswith('git+https://'):
376 url = url[4:]
szager@chromium.org848fd492014-04-09 19:06:44 +0000377 SCMWrapper.__init__(self, url, *args)
378 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
379 if self.out_cb:
380 filter_kwargs['predicate'] = self.out_cb
381 self.filter = gclient_utils.GitFilter(**filter_kwargs)
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000382
mukai@chromium.org9e3e82c2012-04-18 12:55:43 +0000383 @staticmethod
384 def BinaryExists():
385 """Returns true if the command exists."""
386 try:
387 # We assume git is newer than 1.7. See: crbug.com/114483
388 result, version = scm.GIT.AssertVersion('1.7')
389 if not result:
390 raise gclient_utils.Error('Git version is older than 1.7: %s' % version)
391 return result
392 except OSError:
393 return False
394
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000395 def ParseRefish(self, value, **kwargs):
396 kwargs.setdefault('remote', self.remote)
397 return GitRefish.Parse(value, **kwargs)
398
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000399 def GetCheckoutRoot(self):
400 return scm.GIT.GetCheckoutRoot(self.checkout_path)
401
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000402 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000403 """Returns the given revision's date in ISO-8601 format (which contains the
404 time zone)."""
405 # TODO(floitsch): get the time-stamp of the given revision and not just the
406 # time-stamp of the currently checked out revision.
407 return self._Capture(['log', '-n', '1', '--format=%ai'])
408
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000409 @staticmethod
410 def cleanup(options, args, file_list):
msb@chromium.orgd8a63782010-01-25 17:47:05 +0000411 """'Cleanup' the repo.
412
413 There's no real git equivalent for the svn cleanup command, do a no-op.
414 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000415
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000416 def diff(self, options, _args, _file_list):
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000417 merge_base = self._Capture(['merge-base', 'HEAD', self.remote])
maruel@chromium.org37e89872010-09-07 16:11:33 +0000418 self._Run(['diff', merge_base], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000419
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000420 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000421 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000422 repository.
423
424 The patch file is generated from a diff of the merge base of HEAD and
425 its upstream branch.
426 """
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000427 merge_base = self._Capture(['merge-base', 'HEAD', self.remote])
maruel@chromium.org17d01792010-09-01 18:07:10 +0000428 gclient_utils.CheckCallAndFilter(
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000429 ['git', 'diff', merge_base],
430 cwd=self.checkout_path,
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000431 filter_fn=GitDiffFilterer(self.relpath).Filter, print_func=self.Print)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000432
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000433 def _FetchAndReset(self, refish, file_list, options):
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000434 """Equivalent to git fetch; git reset."""
435 quiet = []
436 if not options.verbose:
437 quiet = ['--quiet']
438 self._UpdateBranchHeads(options, fetch=False)
439
dnj@chromium.org680f2172014-06-25 00:39:32 +0000440 self._Fetch(options, prune=True, quiet=options.verbose)
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000441 self._Run(['reset', '--hard', refish.local_ref] + quiet, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000442 if file_list is not None:
443 files = self._Capture(['ls-files']).splitlines()
444 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
445
szager@chromium.org8a139702014-06-20 15:55:01 +0000446 def _DisableHooks(self):
447 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
448 if not os.path.isdir(hook_dir):
449 return
450 for f in os.listdir(hook_dir):
451 if not f.endswith('.sample') and not f.endswith('.disabled'):
452 os.rename(os.path.join(hook_dir, f),
453 os.path.join(hook_dir, f + '.disabled'))
454
msb@chromium.orge28e4982009-09-25 20:51:45 +0000455 def update(self, options, args, file_list):
456 """Runs git to update or transparently checkout the working copy.
457
458 All updated files will be appended to file_list.
459
460 Raises:
461 Error: if can't get URL for relative path.
462 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000463 if args:
464 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
465
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000466 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000467
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000468 # If a dependency is not pinned, track the default remote branch.
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000469 default_rev = 'refs/remotes/%s/master' % (self.remote,)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000470 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000471 rev_str = ""
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000472 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000473 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000474 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000475 # Override the revision number.
476 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000477 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000478 # Check again for a revision in case an initial ref was specified
479 # in the url, for example bla.git@refs/heads/custombranch
480 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000481 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000482 if not revision:
483 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000484
szager@chromium.org8a139702014-06-20 15:55:01 +0000485 if managed:
486 self._DisableHooks()
487
floitsch@google.comeaab7842011-04-28 09:07:58 +0000488 if gclient_utils.IsDateRevision(revision):
489 # Date-revisions only work on git-repositories if the reflog hasn't
490 # expired yet. Use rev-list to get the corresponding revision.
491 # git rev-list -n 1 --before='time-stamp' branchname
492 if options.transitive:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000493 self.Print('Warning: --transitive only works for SVN repositories.')
floitsch@google.comeaab7842011-04-28 09:07:58 +0000494 revision = default_rev
495
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000496 rev_str = ' at %s' % revision
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000497 files = [] if file_list is not None else None
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000498
499 printed_path = False
500 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000501 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000502 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000503 verbose = ['--verbose']
504 printed_path = True
505
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000506 refish = self.ParseRefish(revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000507 mirror = self._GetMirror(url, options)
508 if mirror:
509 url = mirror.mirror_path
510
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000511 if (not os.path.exists(self.checkout_path) or
512 (os.path.isdir(self.checkout_path) and
513 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000514 if mirror:
515 self._UpdateMirror(mirror, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000516 try:
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000517 self._Clone(refish.local_ref, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000518 except subprocess2.CalledProcessError:
519 self._DeleteOrMove(options.force)
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000520 self._Clone(refish.local_ref, url, options)
jochen@chromium.org048da082014-05-06 08:32:40 +0000521 if deps_revision and deps_revision.startswith('branch-heads/'):
522 deps_branch = deps_revision.replace('branch-heads/', '')
523 self._Capture(['branch', deps_branch, deps_revision])
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000524 self._Checkout(options, deps_branch, quiet=True)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000525 if file_list is not None:
526 files = self._Capture(['ls-files']).splitlines()
527 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000528 if not verbose:
529 # Make the output a little prettier. It's nice to have some whitespace
530 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000531 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000532 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000533
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000534 if not managed:
535 self._UpdateBranchHeads(options, fetch=False)
536 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
537 return self._Capture(['rev-parse', '--verify', 'HEAD'])
538
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000539 if mirror:
540 self._UpdateMirror(mirror, options)
541
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000542 # See if the url has changed (the unittests use git://foo for the url, let
543 # that through).
544 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
545 return_early = False
546 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
547 # unit test pass. (and update the comment above)
548 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
549 # This allows devs to use experimental repos which have a different url
550 # but whose branch(s) are the same as official repos.
borenet@google.comb09097a2014-04-09 19:09:08 +0000551 if (current_url.rstrip('/') != url.rstrip('/') and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000552 url != 'git://foo' and
553 subprocess2.capture(
554 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
555 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000556 self.Print('_____ switching %s to a new upstream' % self.relpath)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000557 # Make sure it's clean
558 self._CheckClean(rev_str)
559 # Switch over to the new upstream
560 self._Run(['remote', 'set-url', self.remote, url], options)
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000561 self._FetchAndReset(refish, file_list, options)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000562 return_early = True
563
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000564 if return_early:
565 return self._Capture(['rev-parse', '--verify', 'HEAD'])
566
msb@chromium.org5bde4852009-12-14 16:47:12 +0000567 cur_branch = self._GetCurrentBranch()
568
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000569 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000570 # 0) HEAD is detached. Probably from our initial clone.
571 # - make sure HEAD is contained by a named ref, then update.
572 # Cases 1-4. HEAD is a branch.
573 # 1) current branch is not tracking a remote branch (could be git-svn)
574 # - try to rebase onto the new hash or branch
575 # 2) current branch is tracking a remote branch with local committed
576 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000577 # - rebase those changes on top of the hash
msb@chromium.org786fb682010-06-02 15:16:23 +0000578 # 3) current branch is tracking a remote branch w/or w/out changes,
579 # no switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000580 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
msb@chromium.org786fb682010-06-02 15:16:23 +0000581 # 4) current branch is tracking a remote branch, switches to a different
582 # remote branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000583 # - exit
584
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000585 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
586 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000587 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
588 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000589 if cur_branch is None:
590 upstream_branch = None
591 current_type = "detached"
592 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000593 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000594 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
595 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
596 current_type = "hash"
597 logging.debug("Current branch is not tracking an upstream (remote)"
598 " branch.")
599 elif upstream_branch.startswith('refs/remotes'):
600 current_type = "branch"
601 else:
602 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000603
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000604 if not scm.GIT.IsValidRevision(self.checkout_path, refish.local_ref,
605 sha_only=True):
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000606 # Update the remotes first so we have all the refs.
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000607 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000608 cwd=self.checkout_path)
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000609 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000610 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000611
mmoss@chromium.orge409df62013-04-16 17:28:57 +0000612 self._UpdateBranchHeads(options, fetch=True)
613
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000614 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000615 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000616 target = 'HEAD'
617 if options.upstream and upstream_branch:
618 target = upstream_branch
619 self._Run(['reset', '--hard', target], options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000620
msb@chromium.org786fb682010-06-02 15:16:23 +0000621 if current_type == 'detached':
622 # case 0
623 self._CheckClean(rev_str)
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000624 self._CheckDetachedHead(rev_str, options)
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000625 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == refish.local_ref:
szager@chromium.org848fd492014-04-09 19:06:44 +0000626 self.Print('Up-to-date; skipping checkout.')
627 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000628 # 'git checkout' may need to overwrite existing untracked files. Allow
629 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000630 self._Checkout(
631 options,
632 revision,
633 force=(options.force and options.delete_unversioned_trees),
634 quiet=True,
635 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000636 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000637 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000638 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000639 # case 1
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000640 if scm.GIT.IsGitSvn(self.checkout_path) and upstream_branch is not None:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000641 # Our git-svn branch (upstream_branch) is our upstream
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000642 self._AttemptRebase(upstream_branch, files, options,
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000643 newbase=refish.local_ref,
644 printed_path=printed_path, merge=options.merge)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000645 printed_path = True
646 else:
647 # Can't find a merge-base since we don't know our upstream. That makes
648 # this command VERY likely to produce a rebase failure. For now we
649 # assume origin is our upstream since that's what the old behavior was.
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000650 upstream_branch = self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000651 if options.revision or deps_revision:
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000652 upstream_branch = refish.local_ref
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000653 self._AttemptRebase(upstream_branch, files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000654 printed_path=printed_path, merge=options.merge)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000655 printed_path = True
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000656 elif not refish.is_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000657 # case 2
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000658 self._AttemptRebase(upstream_branch, files, options,
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000659 newbase=refish.local_ref, printed_path=printed_path,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000660 merge=options.merge)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000661 printed_path = True
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000662 elif refish.upstream_branch != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000663 # case 4
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000664 new_base = refish.upstream_branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000665 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000666 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000667 switch_error = ("Switching upstream branch from %s to %s\n"
668 % (upstream_branch, new_base) +
669 "Please merge or rebase manually:\n" +
670 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
671 "OR git checkout -b <some new branch> %s" % new_base)
672 raise gclient_utils.Error(switch_error)
673 else:
674 # case 3 - the default case
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000675 if files is not None:
676 files = self._Capture(['diff', upstream_branch, '--name-only']).split()
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000677 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000678 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000679 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000680 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000681 if options.merge:
682 merge_args.append('--ff')
683 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000684 merge_args.append('--ff-only')
685 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000686 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000687 except subprocess2.CalledProcessError as e:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000688 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000689 files = []
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000690 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000691 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000692 printed_path = True
693 while True:
694 try:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000695 action = self._AskForData(
696 'Cannot %s, attempt to rebase? '
697 '(y)es / (q)uit / (s)kip : ' %
698 ('merge' if options.merge else 'fast-forward merge'),
699 options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000700 except ValueError:
maruel@chromium.org90541732011-04-01 17:54:18 +0000701 raise gclient_utils.Error('Invalid Character')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000702 if re.match(r'yes|y', action, re.I):
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000703 self._AttemptRebase(upstream_branch, files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000704 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000705 printed_path = True
706 break
707 elif re.match(r'quit|q', action, re.I):
708 raise gclient_utils.Error("Can't fast-forward, please merge or "
709 "rebase manually.\n"
710 "cd %s && git " % self.checkout_path
711 + "rebase %s" % upstream_branch)
712 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000713 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000714 return
715 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000716 self.Print('Input not recognized')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000717 elif re.match("error: Your local changes to '.*' would be "
718 "overwritten by merge. Aborting.\nPlease, commit your "
719 "changes or stash them before you can merge.\n",
720 e.stderr):
721 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000722 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000723 printed_path = True
724 raise gclient_utils.Error(e.stderr)
725 else:
726 # Some other problem happened with the merge
727 logging.error("Error during fast-forward merge in %s!" % self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000728 self.Print(e.stderr)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000729 raise
730 else:
731 # Fast-forward merge was successful
732 if not re.match('Already up-to-date.', merge_output) or verbose:
733 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000734 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000735 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000736 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000737 if not verbose:
738 # Make the output a little prettier. It's nice to have some
739 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000740 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000741
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000742 if file_list is not None:
743 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000744
745 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000746 if self._IsRebasing():
msb@chromium.org5bde4852009-12-14 16:47:12 +0000747 raise gclient_utils.Error('\n____ %s%s\n'
748 '\nConflict while rebasing this branch.\n'
749 'Fix the conflict and run gclient again.\n'
750 'See man git-rebase for details.\n'
751 % (self.relpath, rev_str))
752
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000753 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000754 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
755 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000756
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000757 # If --reset and --delete_unversioned_trees are specified, remove any
758 # untracked directories.
759 if options.reset and options.delete_unversioned_trees:
760 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
761 # merge-base by default), so doesn't include untracked files. So we use
762 # 'git ls-files --directory --others --exclude-standard' here directly.
763 paths = scm.GIT.Capture(
764 ['ls-files', '--directory', '--others', '--exclude-standard'],
765 self.checkout_path)
766 for path in (p for p in paths.splitlines() if p.endswith('/')):
767 full_path = os.path.join(self.checkout_path, path)
768 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000769 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000770 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000771
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000772 return self._Capture(['rev-parse', '--verify', 'HEAD'])
773
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000774
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000775 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000776 """Reverts local modifications.
777
778 All reverted files will be appended to file_list.
779 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000780 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000781 # revert won't work if the directory doesn't exist. It needs to
782 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000783 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000784 # Don't reuse the args.
785 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000786
787 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000788 if options.upstream:
789 if self._GetCurrentBranch():
790 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
791 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000792 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000793 if not deps_revision:
794 deps_revision = default_rev
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000795 refish = self.ParseRefish(deps_revision)
796 deps_revision = self.GetUsableRev(refish.remote_refspec, options)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000797
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000798 if file_list is not None:
799 files = self._Capture(['diff', deps_revision, '--name-only']).split()
800
maruel@chromium.org37e89872010-09-07 16:11:33 +0000801 self._Run(['reset', '--hard', deps_revision], options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000802 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000803
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000804 if file_list is not None:
805 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
806
807 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000808 """Returns revision"""
809 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000810
msb@chromium.orge28e4982009-09-25 20:51:45 +0000811 def runhooks(self, options, args, file_list):
812 self.status(options, args, file_list)
813
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000814 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000815 """Display status information."""
816 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000817 self.Print('________ couldn\'t run status in %s:\n'
818 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000819 else:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000820 merge_base = self._Capture(['merge-base', 'HEAD', self.remote])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000821 self._Run(['diff', '--name-status', merge_base], options,
822 stdout=self.out_fh)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000823 if file_list is not None:
824 files = self._Capture(['diff', '--name-only', merge_base]).split()
825 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000826
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000827 def GetUsableRev(self, rev, options):
828 """Finds a useful revision for this repository.
829
830 If SCM is git-svn and the head revision is less than |rev|, git svn fetch
831 will be called on the source."""
832 sha1 = None
iannucci@chromium.org3830a672013-02-19 20:15:14 +0000833 if not os.path.isdir(self.checkout_path):
834 raise gclient_utils.Error(
835 ( 'We could not find a valid hash for safesync_url response "%s".\n'
836 'Safesync URLs with a git checkout currently require the repo to\n'
837 'be cloned without a safesync_url before adding the safesync_url.\n'
838 'For more info, see: '
839 'http://code.google.com/p/chromium/wiki/UsingNewGit'
840 '#Initial_checkout' ) % rev)
841 elif rev.isdigit() and len(rev) < 7:
842 # Handles an SVN rev. As an optimization, only verify an SVN revision as
843 # [0-9]{1,6} for now to avoid making a network request.
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000844 if scm.GIT.IsGitSvn(cwd=self.checkout_path):
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000845 local_head = scm.GIT.GetGitSvnHeadRev(cwd=self.checkout_path)
846 if not local_head or local_head < int(rev):
dbeam@chromium.org2a75fdb2012-02-15 01:32:57 +0000847 try:
848 logging.debug('Looking for git-svn configuration optimizations.')
849 if scm.GIT.Capture(['config', '--get', 'svn-remote.svn.fetch'],
850 cwd=self.checkout_path):
dnj@chromium.org680f2172014-06-25 00:39:32 +0000851 self._Fetch(options)
dbeam@chromium.org2a75fdb2012-02-15 01:32:57 +0000852 except subprocess2.CalledProcessError:
853 logging.debug('git config --get svn-remote.svn.fetch failed, '
854 'ignoring possible optimization.')
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000855 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000856 self.Print('Running git svn fetch. This might take a while.\n')
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000857 scm.GIT.Capture(['svn', 'fetch'], cwd=self.checkout_path)
szager@google.com312a6a42012-10-11 21:19:42 +0000858 try:
szager@chromium.orgc51def32012-10-15 18:50:37 +0000859 sha1 = scm.GIT.GetBlessedSha1ForSvnRev(
860 cwd=self.checkout_path, rev=rev)
szager@google.com312a6a42012-10-11 21:19:42 +0000861 except gclient_utils.Error, e:
862 sha1 = e.message
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000863 self.Print('Warning: Could not find a git revision with accurate\n'
szager@google.com312a6a42012-10-11 21:19:42 +0000864 '.DEPS.git that maps to SVN revision %s. Sync-ing to\n'
865 'the closest sane git revision, which is:\n'
866 ' %s\n' % (rev, e.message))
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000867 if not sha1:
868 raise gclient_utils.Error(
869 ( 'It appears that either your git-svn remote is incorrectly\n'
870 'configured or the revision in your safesync_url is\n'
871 'higher than git-svn remote\'s HEAD as we couldn\'t find a\n'
872 'corresponding git hash for SVN rev %s.' ) % rev)
iannucci@chromium.org3830a672013-02-19 20:15:14 +0000873 else:
874 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
875 sha1 = rev
876 else:
877 # May exist in origin, but we don't have it yet, so fetch and look
878 # again.
dnj@chromium.org680f2172014-06-25 00:39:32 +0000879 self._Fetch(options)
iannucci@chromium.org3830a672013-02-19 20:15:14 +0000880 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
881 sha1 = rev
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000882
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000883 if not sha1:
884 raise gclient_utils.Error(
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000885 ( 'We could not find a valid hash for safesync_url response "%s".\n'
886 'Safesync URLs with a git checkout currently require a git-svn\n'
887 'remote or a safesync_url that provides git sha1s. Please add a\n'
888 'git-svn remote or change your safesync_url. For more info, see:\n'
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000889 'http://code.google.com/p/chromium/wiki/UsingNewGit'
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000890 '#Initial_checkout' ) % rev)
891
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000892 return sha1
893
msb@chromium.orge6f78352010-01-13 17:05:33 +0000894 def FullUrlForRelativeUrl(self, url):
895 # Strip from last '/'
896 # Equivalent to unix basename
897 base_url = self.url
898 return base_url[:base_url.rfind('/')] + url
899
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000900 def _GetMirror(self, url, options):
901 """Get a git_cache.Mirror object for the argument url."""
902 if not git_cache.Mirror.GetCachePath():
903 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000904 mirror_kwargs = {
905 'print_func': self.filter,
906 'refs': []
907 }
hinoka@google.com356cb5f2014-04-25 00:02:21 +0000908 # TODO(hinoka): This currently just fails because lkcr/lkgr are branches
909 # not tags. This also adds 20 seconds to every bot_update
910 # run, so I'm commenting this out until lkcr/lkgr become
911 # tags. (2014/4/24)
912 # if url == CHROMIUM_SRC_URL or url + '.git' == CHROMIUM_SRC_URL:
913 # mirror_kwargs['refs'].extend(['refs/tags/lkgr', 'refs/tags/lkcr'])
hinoka@google.comb1b54572014-04-16 22:29:23 +0000914 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
915 mirror_kwargs['refs'].append('refs/branch-heads/*')
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000916 return git_cache.Mirror(url, **mirror_kwargs)
917
918 @staticmethod
919 def _UpdateMirror(mirror, options):
920 """Update a git mirror by fetching the latest commits from the remote."""
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000921 if options.shallow:
922 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000923 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000924 depth = 10
925 else:
926 depth = 10000
927 else:
928 depth = None
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +0000929 mirror.populate(verbose=options.verbose, bootstrap=True, depth=depth,
930 ignore_lock=options.ignore_locks)
szager@chromium.org848fd492014-04-09 19:06:44 +0000931 mirror.unlock()
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000932
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000933 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000934 """Clone a git repository from the given URL.
935
msb@chromium.org786fb682010-06-02 15:16:23 +0000936 Once we've cloned the repo, we checkout a working branch if the specified
937 revision is a branch head. If it is a tag or a specific commit, then we
938 leave HEAD detached as it makes future updates simpler -- in this case the
939 user should first create a new branch or switch to an existing branch before
940 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000941 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000942 # git clone doesn't seem to insert a newline properly before printing
943 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000944 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +0000945 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +0000946 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000947 if self.cache_dir:
948 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000949 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000950 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000951 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +0000952 # If the parent directory does not exist, Git clone on Windows will not
953 # create it, so we need to do it manually.
954 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000955 gclient_utils.safe_makedirs(parent_dir)
956 tmp_dir = tempfile.mkdtemp(
957 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
958 dir=parent_dir)
959 try:
960 clone_cmd.append(tmp_dir)
agable@chromium.orgfd5b6382013-10-25 20:54:34 +0000961 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000962 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000963 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
964 os.path.join(self.checkout_path, '.git'))
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000965 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000966 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000967 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000968 finally:
969 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000970 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000971 gclient_utils.rmtree(tmp_dir)
mmoss@chromium.org1a6bec02014-06-02 21:53:29 +0000972 self._UpdateBranchHeads(options, fetch=True)
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000973
974 refish = self.ParseRefish(revision)
975 self._Checkout(options, refish.local_ref, quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000976 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +0000977 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000978 self.Print(
dnj@chromium.orgf2707a62014-07-07 17:08:48 +0000979 "Checked out %s to a detached HEAD. Before making any commits\n"
980 "in this repo, you should use 'git checkout <branch>' to switch to\n"
981 "an existing branch or use 'git checkout %s -b <branch>' to\n"
982 "create a new branch for your work." % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000983
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000984 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000985 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000986 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000987 raise gclient_utils.Error("Background task requires input. Rerun "
988 "gclient with --jobs=1 so that\n"
989 "interaction is possible.")
990 try:
991 return raw_input(prompt)
992 except KeyboardInterrupt:
993 # Hide the exception.
994 sys.exit(1)
995
996
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000997 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000998 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000999 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001000 if files is not None:
1001 files.extend(self._Capture(['diff', upstream, '--name-only']).split())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001002 revision = upstream
1003 if newbase:
1004 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001005 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001006 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001007 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001008 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001009 printed_path = True
1010 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001011 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001012
1013 if merge:
1014 merge_output = self._Capture(['merge', revision])
1015 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001016 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001017 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001018
1019 # Build the rebase command here using the args
1020 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1021 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001022 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001023 rebase_cmd.append('--verbose')
1024 if newbase:
1025 rebase_cmd.extend(['--onto', newbase])
1026 rebase_cmd.append(upstream)
1027 if branch:
1028 rebase_cmd.append(branch)
1029
1030 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001031 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001032 except subprocess2.CalledProcessError, e:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001033 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
1034 re.match(r'cannot rebase: your index contains uncommitted changes',
1035 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001036 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001037 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001038 'Cannot rebase because of unstaged changes.\n'
1039 '\'git reset --hard HEAD\' ?\n'
1040 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001041 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001042 if re.match(r'yes|y', rebase_action, re.I):
maruel@chromium.org37e89872010-09-07 16:11:33 +00001043 self._Run(['reset', '--hard', 'HEAD'], options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001044 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001045 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001046 break
1047 elif re.match(r'quit|q', rebase_action, re.I):
1048 raise gclient_utils.Error("Please merge or rebase manually\n"
1049 "cd %s && git " % self.checkout_path
1050 + "%s" % ' '.join(rebase_cmd))
1051 elif re.match(r'show|s', rebase_action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001052 self.Print('%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001053 continue
1054 else:
1055 gclient_utils.Error("Input not recognized")
1056 continue
1057 elif re.search(r'^CONFLICT', e.stdout, re.M):
1058 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1059 "Fix the conflict and run gclient again.\n"
1060 "See 'man git-rebase' for details.\n")
1061 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001062 self.Print(e.stdout.strip())
1063 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001064 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1065 "manually.\ncd %s && git " %
1066 self.checkout_path
1067 + "%s" % ' '.join(rebase_cmd))
1068
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001069 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001070 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001071 # Make the output a little prettier. It's nice to have some
1072 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001073 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001074
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001075 @staticmethod
1076 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001077 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1078 if not ok:
1079 raise gclient_utils.Error('git version %s < minimum required %s' %
1080 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001081
msb@chromium.org786fb682010-06-02 15:16:23 +00001082 def _IsRebasing(self):
1083 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1084 # have a plumbing command to determine whether a rebase is in progress, so
1085 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1086 g = os.path.join(self.checkout_path, '.git')
1087 return (
1088 os.path.isdir(os.path.join(g, "rebase-merge")) or
1089 os.path.isdir(os.path.join(g, "rebase-apply")))
1090
1091 def _CheckClean(self, rev_str):
1092 # Make sure the tree is clean; see git-rebase.sh for reference
1093 try:
1094 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001095 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001096 except subprocess2.CalledProcessError:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001097 raise gclient_utils.Error('\n____ %s%s\n'
1098 '\tYou have unstaged changes.\n'
1099 '\tPlease commit, stash, or reset.\n'
1100 % (self.relpath, rev_str))
msb@chromium.org786fb682010-06-02 15:16:23 +00001101 try:
1102 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001103 '--ignore-submodules', 'HEAD', '--'],
1104 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001105 except subprocess2.CalledProcessError:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001106 raise gclient_utils.Error('\n____ %s%s\n'
1107 '\tYour index contains uncommitted changes\n'
1108 '\tPlease commit, stash, or reset.\n'
1109 % (self.relpath, rev_str))
msb@chromium.org786fb682010-06-02 15:16:23 +00001110
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001111 def _CheckDetachedHead(self, rev_str, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001112 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1113 # reference by a commit). If not, error out -- most likely a rebase is
1114 # in progress, try to detect so we can give a better error.
1115 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001116 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1117 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001118 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001119 # Commit is not contained by any rev. See if the user is rebasing:
1120 if self._IsRebasing():
1121 # Punt to the user
1122 raise gclient_utils.Error('\n____ %s%s\n'
1123 '\tAlready in a conflict, i.e. (no branch).\n'
1124 '\tFix the conflict and run gclient again.\n'
1125 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1126 '\tSee man git-rebase for details.\n'
1127 % (self.relpath, rev_str))
1128 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001129 name = ('saved-by-gclient-' +
1130 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001131 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001132 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001133 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001134
msb@chromium.org5bde4852009-12-14 16:47:12 +00001135 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001136 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001137 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001138 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001139 return None
1140 return branch
1141
borenet@google.comc3e09d22014-04-10 13:58:18 +00001142 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001143 kwargs.setdefault('cwd', self.checkout_path)
1144 kwargs.setdefault('stderr', subprocess2.PIPE)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001145 env = scm.GIT.ApplyEnvVars(kwargs)
1146 return subprocess2.check_output(['git'] + args, env=env, **kwargs).strip()
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001147
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001148 def _Checkout(self, options, ref, force=False, quiet=None):
1149 """Performs a 'git-checkout' operation.
1150
1151 Args:
1152 options: The configured option set
1153 ref: (str) The branch/commit to checkout
1154 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1155 'None', the behavior is inferred from 'options.verbose'.
1156 Returns: (str) The output of the checkout operation
1157 """
1158 if quiet is None:
1159 quiet = (not options.verbose)
1160 checkout_args = ['checkout']
1161 if force:
1162 checkout_args.append('--force')
1163 if quiet:
1164 checkout_args.append('--quiet')
1165 checkout_args.append(ref)
1166 return self._Capture(checkout_args)
1167
dnj@chromium.org680f2172014-06-25 00:39:32 +00001168 def _Fetch(self, options, remote=None, prune=False, quiet=False):
1169 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
1170 fetch_cmd = cfg + [
1171 'fetch',
1172 remote or self.remote,
1173 ]
1174
1175 if prune:
1176 fetch_cmd.append('--prune')
1177 if options.verbose:
1178 fetch_cmd.append('--verbose')
1179 elif quiet:
1180 fetch_cmd.append('--quiet')
1181 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
1182
1183 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1184 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1185
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001186 def _UpdateBranchHeads(self, options, fetch=False):
1187 """Adds, and optionally fetches, "branch-heads" refspecs if requested."""
1188 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001189 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001190 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1191 '^\\+refs/branch-heads/\\*:.*$']
1192 self._Run(config_cmd, options)
1193 if fetch:
dnj@chromium.org680f2172014-06-25 00:39:32 +00001194 self._Fetch(options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001195
dnj@chromium.org680f2172014-06-25 00:39:32 +00001196 def _Run(self, args, options, show_header=True, **kwargs):
1197 # Disable 'unused options' warning | pylint: disable=W0613
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001198 cwd = kwargs.setdefault('cwd', self.checkout_path)
1199 kwargs.setdefault('stdout', self.out_fh)
szager@chromium.org848fd492014-04-09 19:06:44 +00001200 kwargs['filter_fn'] = self.filter
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001201 kwargs.setdefault('print_stdout', False)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001202 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001203 cmd = ['git'] + args
dnj@chromium.org680f2172014-06-25 00:39:32 +00001204 if show_header:
1205 header = "running '%s' in '%s'" % (' '.join(cmd), cwd)
1206 self.filter(header)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001207 return gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
msb@chromium.orge28e4982009-09-25 20:51:45 +00001208
1209
maruel@chromium.org55e724e2010-03-11 19:36:49 +00001210class SVNWrapper(SCMWrapper):
msb@chromium.orgcb5442b2009-09-22 16:51:24 +00001211 """ Wrapper for SVN """
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001212 name = 'svn'
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001213
mukai@chromium.org9e3e82c2012-04-18 12:55:43 +00001214 @staticmethod
1215 def BinaryExists():
1216 """Returns true if the command exists."""
1217 try:
1218 result, version = scm.SVN.AssertVersion('1.4')
1219 if not result:
1220 raise gclient_utils.Error('SVN version is older than 1.4: %s' % version)
1221 return result
1222 except OSError:
1223 return False
1224
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001225 def GetCheckoutRoot(self):
1226 return scm.SVN.GetCheckoutRoot(self.checkout_path)
1227
floitsch@google.comeaab7842011-04-28 09:07:58 +00001228 def GetRevisionDate(self, revision):
1229 """Returns the given revision's date in ISO-8601 format (which contains the
1230 time zone)."""
maruel@chromium.orgd579fcf2011-12-13 20:36:03 +00001231 date = scm.SVN.Capture(
1232 ['propget', '--revprop', 'svn:date', '-r', revision],
1233 os.path.join(self.checkout_path, '.'))
floitsch@google.comeaab7842011-04-28 09:07:58 +00001234 return date.strip()
1235
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001236 def cleanup(self, options, args, _file_list):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001237 """Cleanup working copy."""
maruel@chromium.org669600d2010-09-01 19:06:31 +00001238 self._Run(['cleanup'] + args, options)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001239
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001240 def diff(self, options, args, _file_list):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001241 # NOTE: This function does not currently modify file_list.
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001242 if not os.path.isdir(self.checkout_path):
1243 raise gclient_utils.Error('Directory %s is not present.' %
1244 self.checkout_path)
maruel@chromium.org669600d2010-09-01 19:06:31 +00001245 self._Run(['diff'] + args, options)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001246
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001247 def pack(self, _options, args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +00001248 """Generates a patch file which can be applied to the root of the
1249 repository."""
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001250 if not os.path.isdir(self.checkout_path):
1251 raise gclient_utils.Error('Directory %s is not present.' %
1252 self.checkout_path)
1253 gclient_utils.CheckCallAndFilter(
1254 ['svn', 'diff', '-x', '--ignore-eol-style'] + args,
1255 cwd=self.checkout_path,
1256 print_stdout=False,
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001257 filter_fn=SvnDiffFilterer(self.relpath).Filter, print_func=self.Print)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +00001258
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001259 def update(self, options, args, file_list):
msb@chromium.orgd6504212010-01-13 17:34:31 +00001260 """Runs svn to update or transparently checkout the working copy.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001261
1262 All updated files will be appended to file_list.
1263
1264 Raises:
1265 Error: if can't get URL for relative path.
1266 """
borenet@google.comb09097a2014-04-09 19:09:08 +00001267 # Only update if hg is not controlling the directory.
szager@chromium.org6c2b49d2014-02-26 23:57:38 +00001268 hg_path = os.path.join(self.checkout_path, '.hg')
1269 if os.path.exists(hg_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001270 self.Print('________ found .hg directory; skipping %s' % self.relpath)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +00001271 return
1272
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001273 if args:
1274 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
1275
maruel@chromium.org8e0e9262010-08-17 19:20:27 +00001276 # revision is the revision to match. It is None if no revision is specified,
1277 # i.e. the 'deps ain't pinned'.
msb@chromium.orgac915bb2009-11-13 17:03:01 +00001278 url, revision = gclient_utils.SplitUrlRevision(self.url)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +00001279 # Keep the original unpinned url for reference in case the repo is switched.
1280 base_url = url
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001281 managed = True
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001282 if options.revision:
1283 # Override the revision number.
msb@chromium.orgac915bb2009-11-13 17:03:01 +00001284 revision = str(options.revision)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001285 if revision:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001286 if revision != 'unmanaged':
1287 forced_revision = True
1288 # Reconstruct the url.
1289 url = '%s@%s' % (url, revision)
1290 rev_str = ' at %s' % revision
1291 else:
1292 managed = False
1293 revision = None
maruel@chromium.org8e0e9262010-08-17 19:20:27 +00001294 else:
1295 forced_revision = False
1296 rev_str = ''
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001297
thakis@chromium.orgd0b0a5b2014-03-10 19:30:21 +00001298 exists = os.path.exists(self.checkout_path)
borenet@google.com7ff04292014-03-10 12:57:25 +00001299 if exists and managed:
borenet@google.comb09097a2014-04-09 19:09:08 +00001300 # Git is only okay if it's a git-svn checkout of the right repo.
1301 if scm.GIT.IsGitSvn(self.checkout_path):
1302 remote_url = scm.GIT.Capture(['config', '--local', '--get',
1303 'svn-remote.svn.url'],
1304 cwd=self.checkout_path).rstrip()
1305 if remote_url.rstrip('/') == base_url.rstrip('/'):
borenet@google.comb2256212014-05-07 20:57:28 +00001306 self.Print('\n_____ %s looks like a git-svn checkout. Skipping.'
1307 % self.relpath)
borenet@google.comb09097a2014-04-09 19:09:08 +00001308 return # TODO(borenet): Get the svn revision number?
1309
1310 # Get the existing scm url and the revision number of the current checkout.
1311 if exists and managed:
davidjames@chromium.org13349e22012-11-15 17:11:28 +00001312 try:
1313 from_info = scm.SVN.CaptureLocalInfo(
1314 [], os.path.join(self.checkout_path, '.'))
1315 except (gclient_utils.Error, subprocess2.CalledProcessError):
borenet@google.comb09097a2014-04-09 19:09:08 +00001316 self._DeleteOrMove(options.force)
1317 exists = False
davidjames@chromium.org13349e22012-11-15 17:11:28 +00001318
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001319 BASE_URLS = {
1320 '/chrome/trunk/src': 'gs://chromium-svn-checkout/chrome/',
1321 '/blink/trunk': 'gs://chromium-svn-checkout/blink/',
1322 }
hinoka@google.comca35be32014-01-17 01:48:18 +00001323 WHITELISTED_ROOTS = [
1324 'svn://svn.chromium.org',
1325 'svn://svn-mirror.golo.chromium.org',
1326 ]
davidjames@chromium.org13349e22012-11-15 17:11:28 +00001327 if not exists:
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001328 try:
1329 # Split out the revision number since it's not useful for us.
1330 base_path = urlparse.urlparse(url).path.split('@')[0]
hinoka@google.comca35be32014-01-17 01:48:18 +00001331 # Check to see if we're on a whitelisted root. We do this because
1332 # only some svn servers have matching UUIDs.
1333 local_parsed = urlparse.urlparse(url)
1334 local_root = '%s://%s' % (local_parsed.scheme, local_parsed.netloc)
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001335 if ('CHROME_HEADLESS' in os.environ
1336 and sys.platform == 'linux2' # TODO(hinoka): Enable for win/mac.
hinoka@google.comca35be32014-01-17 01:48:18 +00001337 and base_path in BASE_URLS
1338 and local_root in WHITELISTED_ROOTS):
1339
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001340 # Use a tarball for initial sync if we are on a bot.
1341 # Get an unauthenticated gsutil instance.
1342 gsutil = download_from_google_storage.Gsutil(
1343 GSUTIL_DEFAULT_PATH, boto_path=os.devnull)
1344
1345 gs_path = BASE_URLS[base_path]
1346 _, out, _ = gsutil.check_call('ls', gs_path)
1347 # So that we can get the most recent revision.
1348 sorted_items = sorted(out.splitlines())
1349 latest_checkout = sorted_items[-1]
1350
1351 tempdir = tempfile.mkdtemp()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001352 self.Print('Downloading %s...' % latest_checkout)
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001353 code, out, err = gsutil.check_call('cp', latest_checkout, tempdir)
1354 if code:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001355 self.Print('%s\n%s' % (out, err))
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001356 raise Exception()
1357 filename = latest_checkout.split('/')[-1]
1358 tarball = os.path.join(tempdir, filename)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001359 self.Print('Unpacking into %s...' % self.checkout_path)
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001360 gclient_utils.safe_makedirs(self.checkout_path)
1361 # TODO(hinoka): Use 7z for windows.
1362 cmd = ['tar', '--extract', '--ungzip',
1363 '--directory', self.checkout_path,
1364 '--file', tarball]
1365 gclient_utils.CheckCallAndFilter(
1366 cmd, stdout=sys.stdout, print_stdout=True)
1367
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001368 self.Print('Deleting temp file')
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001369 gclient_utils.rmtree(tempdir)
1370
1371 # Rewrite the repository root to match.
1372 tarball_url = scm.SVN.CaptureLocalInfo(
1373 ['.'], self.checkout_path)['Repository Root']
1374 tarball_parsed = urlparse.urlparse(tarball_url)
1375 tarball_root = '%s://%s' % (tarball_parsed.scheme,
1376 tarball_parsed.netloc)
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001377
1378 if tarball_root != local_root:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001379 self.Print('Switching repository root to %s' % local_root)
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001380 self._Run(['switch', '--relocate', tarball_root,
1381 local_root, self.checkout_path],
1382 options)
1383 except Exception as e:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001384 self.Print('We tried to get a source tarball but failed.')
1385 self.Print('Resuming normal operations.')
1386 self.Print(str(e))
hinoka@google.com2f2ca142014-01-07 03:59:18 +00001387
maruel@chromium.org6c48a302011-10-20 23:44:20 +00001388 gclient_utils.safe_makedirs(os.path.dirname(self.checkout_path))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001389 # We need to checkout.
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001390 command = ['checkout', url, self.checkout_path]
maruel@chromium.org8e0e9262010-08-17 19:20:27 +00001391 command = self._AddAdditionalUpdateFlags(command, options, revision)
maruel@chromium.org669600d2010-09-01 19:06:31 +00001392 self._RunAndGetFileList(command, options, file_list, self._root_dir)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001393 return self.Svnversion()
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001394
szager@chromium.org6c2b49d2014-02-26 23:57:38 +00001395 if not managed:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001396 self.Print(('________ unmanaged solution; skipping %s' % self.relpath))
borenet@google.comb09097a2014-04-09 19:09:08 +00001397 if os.path.exists(os.path.join(self.checkout_path, '.svn')):
1398 return self.Svnversion()
1399 return
szager@chromium.org6c2b49d2014-02-26 23:57:38 +00001400
maruel@chromium.org49fcb0c2011-09-23 14:34:38 +00001401 if 'URL' not in from_info:
1402 raise gclient_utils.Error(
1403 ('gclient is confused. Couldn\'t get the url for %s.\n'
1404 'Try using @unmanaged.\n%s') % (
1405 self.checkout_path, from_info))
1406
stip@chromium.org3031d732014-04-21 22:18:02 +00001407 # Look for locked directories.
1408 dir_info = scm.SVN.CaptureStatus(
1409 None, os.path.join(self.checkout_path, '.'))
1410 if any(d[0][2] == 'L' for d in dir_info):
1411 try:
1412 self._Run(['cleanup', self.checkout_path], options)
1413 except subprocess2.CalledProcessError, e:
1414 # Get the status again, svn cleanup may have cleaned up at least
1415 # something.
1416 dir_info = scm.SVN.CaptureStatus(
1417 None, os.path.join(self.checkout_path, '.'))
1418
phajdan.jr@chromium.orgd558c4b2011-09-22 18:56:24 +00001419 # Try to fix the failures by removing troublesome files.
1420 for d in dir_info:
1421 if d[0][2] == 'L':
1422 if d[0][0] == '!' and options.force:
kustermann@google.com1580d952013-08-19 07:31:40 +00001423 # We don't pass any files/directories to CaptureStatus and set
1424 # cwd=self.checkout_path, so we should get relative paths here.
1425 assert not os.path.isabs(d[1])
1426 path_to_remove = os.path.normpath(
1427 os.path.join(self.checkout_path, d[1]))
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001428 self.Print('Removing troublesome path %s' % path_to_remove)
kustermann@google.com1580d952013-08-19 07:31:40 +00001429 gclient_utils.rmtree(path_to_remove)
phajdan.jr@chromium.orgd558c4b2011-09-22 18:56:24 +00001430 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001431 self.Print(
1432 'Not removing troublesome path %s automatically.' % d[1])
phajdan.jr@chromium.orgd558c4b2011-09-22 18:56:24 +00001433 if d[0][0] == '!':
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001434 self.Print('You can pass --force to enable automatic removal.')
phajdan.jr@chromium.orgd558c4b2011-09-22 18:56:24 +00001435 raise e
maruel@chromium.orge407c9a2010-08-09 19:11:37 +00001436
maruel@chromium.org8e0e9262010-08-17 19:20:27 +00001437 # Retrieve the current HEAD version because svn is slow at null updates.
1438 if options.manually_grab_svn_rev and not revision:
maruel@chromium.orgd579fcf2011-12-13 20:36:03 +00001439 from_info_live = scm.SVN.CaptureRemoteInfo(from_info['URL'])
maruel@chromium.org8e0e9262010-08-17 19:20:27 +00001440 revision = str(from_info_live['Revision'])
1441 rev_str = ' at %s' % revision
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001442
borenet@google.comb09097a2014-04-09 19:09:08 +00001443 if from_info['URL'].rstrip('/') != base_url.rstrip('/'):
szager@chromium.org6c2b49d2014-02-26 23:57:38 +00001444 # The repository url changed, need to switch.
1445 try:
1446 to_info = scm.SVN.CaptureRemoteInfo(url)
1447 except (gclient_utils.Error, subprocess2.CalledProcessError):
1448 # The url is invalid or the server is not accessible, it's safer to bail
1449 # out right now.
1450 raise gclient_utils.Error('This url is unreachable: %s' % url)
1451 can_switch = ((from_info['Repository Root'] != to_info['Repository Root'])
1452 and (from_info['UUID'] == to_info['UUID']))
1453 if can_switch:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001454 self.Print('_____ relocating %s to a new checkout' % self.relpath)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +00001455 # We have different roots, so check if we can switch --relocate.
1456 # Subversion only permits this if the repository UUIDs match.
1457 # Perform the switch --relocate, then rewrite the from_url
1458 # to reflect where we "are now." (This is the same way that
1459 # Subversion itself handles the metadata when switch --relocate
1460 # is used.) This makes the checks below for whether we
1461 # can update to a revision or have to switch to a different
1462 # branch work as expected.
1463 # TODO(maruel): TEST ME !
1464 command = ['switch', '--relocate',
1465 from_info['Repository Root'],
1466 to_info['Repository Root'],
1467 self.relpath]
1468 self._Run(command, options, cwd=self._root_dir)
1469 from_info['URL'] = from_info['URL'].replace(
1470 from_info['Repository Root'],
1471 to_info['Repository Root'])
1472 else:
1473 if not options.force and not options.reset:
1474 # Look for local modifications but ignore unversioned files.
1475 for status in scm.SVN.CaptureStatus(None, self.checkout_path):
1476 if status[0][0] != '?':
1477 raise gclient_utils.Error(
1478 ('Can\'t switch the checkout to %s; UUID don\'t match and '
1479 'there is local changes in %s. Delete the directory and '
1480 'try again.') % (url, self.checkout_path))
1481 # Ok delete it.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001482 self.Print('_____ switching %s to a new checkout' % self.relpath)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +00001483 gclient_utils.rmtree(self.checkout_path)
1484 # We need to checkout.
1485 command = ['checkout', url, self.checkout_path]
1486 command = self._AddAdditionalUpdateFlags(command, options, revision)
1487 self._RunAndGetFileList(command, options, file_list, self._root_dir)
1488 return self.Svnversion()
1489
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001490 # If the provided url has a revision number that matches the revision
1491 # number of the existing directory, then we don't need to bother updating.
maruel@chromium.org2e0c6852009-09-24 00:02:07 +00001492 if not options.force and str(from_info['Revision']) == revision:
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001493 if options.verbose or not forced_revision:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001494 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False)
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001495 else:
1496 command = ['update', self.checkout_path]
1497 command = self._AddAdditionalUpdateFlags(command, options, revision)
1498 self._RunAndGetFileList(command, options, file_list, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001499
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001500 # If --reset and --delete_unversioned_trees are specified, remove any
1501 # untracked files and directories.
1502 if options.reset and options.delete_unversioned_trees:
1503 for status in scm.SVN.CaptureStatus(None, self.checkout_path):
1504 full_path = os.path.join(self.checkout_path, status[1])
1505 if (status[0][0] == '?'
1506 and os.path.isdir(full_path)
1507 and not os.path.islink(full_path)):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001508 self.Print('_____ removing unversioned directory %s' % status[1])
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001509 gclient_utils.rmtree(full_path)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001510 return self.Svnversion()
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001511
tony@chromium.org4b5b1772010-04-08 01:52:56 +00001512 def updatesingle(self, options, args, file_list):
tony@chromium.org4b5b1772010-04-08 01:52:56 +00001513 filename = args.pop()
tony@chromium.org57564662010-04-14 02:35:12 +00001514 if scm.SVN.AssertVersion("1.5")[0]:
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001515 if not os.path.exists(os.path.join(self.checkout_path, '.svn')):
tony@chromium.org57564662010-04-14 02:35:12 +00001516 # Create an empty checkout and then update the one file we want. Future
1517 # operations will only apply to the one file we checked out.
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001518 command = ["checkout", "--depth", "empty", self.url, self.checkout_path]
maruel@chromium.org669600d2010-09-01 19:06:31 +00001519 self._Run(command, options, cwd=self._root_dir)
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001520 if os.path.exists(os.path.join(self.checkout_path, filename)):
1521 os.remove(os.path.join(self.checkout_path, filename))
tony@chromium.org57564662010-04-14 02:35:12 +00001522 command = ["update", filename]
maruel@chromium.org669600d2010-09-01 19:06:31 +00001523 self._RunAndGetFileList(command, options, file_list)
tony@chromium.org57564662010-04-14 02:35:12 +00001524 # After the initial checkout, we can use update as if it were any other
1525 # dep.
1526 self.update(options, args, file_list)
1527 else:
1528 # If the installed version of SVN doesn't support --depth, fallback to
1529 # just exporting the file. This has the downside that revision
1530 # information is not stored next to the file, so we will have to
1531 # re-export the file every time we sync.
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001532 if not os.path.exists(self.checkout_path):
maruel@chromium.org6c48a302011-10-20 23:44:20 +00001533 gclient_utils.safe_makedirs(self.checkout_path)
tony@chromium.org57564662010-04-14 02:35:12 +00001534 command = ["export", os.path.join(self.url, filename),
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001535 os.path.join(self.checkout_path, filename)]
maruel@chromium.org8e0e9262010-08-17 19:20:27 +00001536 command = self._AddAdditionalUpdateFlags(command, options,
1537 options.revision)
maruel@chromium.org669600d2010-09-01 19:06:31 +00001538 self._Run(command, options, cwd=self._root_dir)
tony@chromium.org4b5b1772010-04-08 01:52:56 +00001539
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001540 def revert(self, options, _args, file_list):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001541 """Reverts local modifications. Subversion specific.
1542
1543 All reverted files will be appended to file_list, even if Subversion
1544 doesn't know about them.
1545 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001546 if not os.path.isdir(self.checkout_path):
maruel@chromium.orgc0cc0872011-10-12 17:02:41 +00001547 if os.path.exists(self.checkout_path):
1548 gclient_utils.rmtree(self.checkout_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001549 # svn revert won't work if the directory doesn't exist. It needs to
1550 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001551 self.Print('_____ %s is missing, synching instead' % self.relpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001552 # Don't reuse the args.
1553 return self.update(options, [], file_list)
1554
maruel@chromium.orgc0cc0872011-10-12 17:02:41 +00001555 if not os.path.isdir(os.path.join(self.checkout_path, '.svn')):
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +00001556 if os.path.isdir(os.path.join(self.checkout_path, '.git')):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001557 self.Print('________ found .git directory; skipping %s' % self.relpath)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +00001558 return
szager@chromium.org6c2b49d2014-02-26 23:57:38 +00001559 if os.path.isdir(os.path.join(self.checkout_path, '.hg')):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001560 self.Print('________ found .hg directory; skipping %s' % self.relpath)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +00001561 return
maruel@chromium.orgc0cc0872011-10-12 17:02:41 +00001562 if not options.force:
1563 raise gclient_utils.Error('Invalid checkout path, aborting')
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001564 self.Print(
maruel@chromium.orgc0cc0872011-10-12 17:02:41 +00001565 '\n_____ %s is not a valid svn checkout, synching instead' %
1566 self.relpath)
1567 gclient_utils.rmtree(self.checkout_path)
1568 # Don't reuse the args.
1569 return self.update(options, [], file_list)
1570
maruel@chromium.org07ab60e2011-02-08 21:54:00 +00001571 def printcb(file_status):
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001572 if file_list is not None:
1573 file_list.append(file_status[1])
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +00001574 if logging.getLogger().isEnabledFor(logging.INFO):
maruel@chromium.org07ab60e2011-02-08 21:54:00 +00001575 logging.info('%s%s' % (file_status[0], file_status[1]))
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +00001576 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001577 self.Print(os.path.join(self.checkout_path, file_status[1]))
maruel@chromium.org07ab60e2011-02-08 21:54:00 +00001578 scm.SVN.Revert(self.checkout_path, callback=printcb)
maruel@chromium.orgaa3dd472009-09-21 19:02:48 +00001579
maruel@chromium.org8b322b32011-11-01 19:05:50 +00001580 # Revert() may delete the directory altogether.
1581 if not os.path.isdir(self.checkout_path):
1582 # Don't reuse the args.
1583 return self.update(options, [], file_list)
1584
maruel@chromium.org810a50b2009-10-05 23:03:18 +00001585 try:
1586 # svn revert is so broken we don't even use it. Using
1587 # "svn up --revision BASE" achieve the same effect.
maruel@chromium.org07ab60e2011-02-08 21:54:00 +00001588 # file_list will contain duplicates.
maruel@chromium.org669600d2010-09-01 19:06:31 +00001589 self._RunAndGetFileList(['update', '--revision', 'BASE'], options,
1590 file_list)
maruel@chromium.org810a50b2009-10-05 23:03:18 +00001591 except OSError, e:
maruel@chromium.org07ab60e2011-02-08 21:54:00 +00001592 # Maybe the directory disapeared meanwhile. Do not throw an exception.
maruel@chromium.org810a50b2009-10-05 23:03:18 +00001593 logging.error('Failed to update:\n%s' % str(e))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001594
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001595 def revinfo(self, _options, _args, _file_list):
msb@chromium.org0f282062009-11-06 20:14:02 +00001596 """Display revision"""
maruel@chromium.org54019f32010-09-09 13:50:11 +00001597 try:
1598 return scm.SVN.CaptureRevision(self.checkout_path)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001599 except (gclient_utils.Error, subprocess2.CalledProcessError):
maruel@chromium.org54019f32010-09-09 13:50:11 +00001600 return None
msb@chromium.org0f282062009-11-06 20:14:02 +00001601
msb@chromium.orgcb5442b2009-09-22 16:51:24 +00001602 def runhooks(self, options, args, file_list):
1603 self.status(options, args, file_list)
1604
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001605 def status(self, options, args, file_list):
1606 """Display status information."""
maruel@chromium.org669600d2010-09-01 19:06:31 +00001607 command = ['status'] + args
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001608 if not os.path.isdir(self.checkout_path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001609 # svn status won't work if the directory doesn't exist.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001610 self.Print(('\n________ couldn\'t run \'%s\' in \'%s\':\n'
maruel@chromium.org77e4eca2010-09-21 13:23:07 +00001611 'The directory does not exist.') %
1612 (' '.join(command), self.checkout_path))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001613 # There's no file list to retrieve.
1614 else:
maruel@chromium.org669600d2010-09-01 19:06:31 +00001615 self._RunAndGetFileList(command, options, file_list)
msb@chromium.orge6f78352010-01-13 17:05:33 +00001616
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001617 def GetUsableRev(self, rev, _options):
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +00001618 """Verifies the validity of the revision for this repository."""
1619 if not scm.SVN.IsValidRevision(url='%s@%s' % (self.url, rev)):
1620 raise gclient_utils.Error(
1621 ( '%s isn\'t a valid revision. Please check that your safesync_url is\n'
1622 'correct.') % rev)
1623 return rev
1624
msb@chromium.orge6f78352010-01-13 17:05:33 +00001625 def FullUrlForRelativeUrl(self, url):
1626 # Find the forth '/' and strip from there. A bit hackish.
1627 return '/'.join(self.url.split('/')[:4]) + url
tony@chromium.org99828122010-06-04 01:41:02 +00001628
maruel@chromium.org669600d2010-09-01 19:06:31 +00001629 def _Run(self, args, options, **kwargs):
1630 """Runs a commands that goes to stdout."""
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001631 kwargs.setdefault('cwd', self.checkout_path)
maruel@chromium.org669600d2010-09-01 19:06:31 +00001632 gclient_utils.CheckCallAndFilterAndHeader(['svn'] + args,
maruel@chromium.org77e4eca2010-09-21 13:23:07 +00001633 always=options.verbose, **kwargs)
maruel@chromium.org669600d2010-09-01 19:06:31 +00001634
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001635 def Svnversion(self):
1636 """Runs the lowest checked out revision in the current project."""
1637 info = scm.SVN.CaptureLocalInfo([], os.path.join(self.checkout_path, '.'))
1638 return info['Revision']
1639
maruel@chromium.org669600d2010-09-01 19:06:31 +00001640 def _RunAndGetFileList(self, args, options, file_list, cwd=None):
1641 """Runs a commands that goes to stdout and grabs the file listed."""
maruel@chromium.org8469bf92010-09-03 19:03:15 +00001642 cwd = cwd or self.checkout_path
maruel@chromium.orgce117f62011-01-17 20:04:25 +00001643 scm.SVN.RunAndGetFileList(
1644 options.verbose,
1645 args + ['--ignore-externals'],
1646 cwd=cwd,
maruel@chromium.org77e4eca2010-09-21 13:23:07 +00001647 file_list=file_list)
maruel@chromium.org669600d2010-09-01 19:06:31 +00001648
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001649 @staticmethod
maruel@chromium.org8e0e9262010-08-17 19:20:27 +00001650 def _AddAdditionalUpdateFlags(command, options, revision):
tony@chromium.org99828122010-06-04 01:41:02 +00001651 """Add additional flags to command depending on what options are set.
1652 command should be a list of strings that represents an svn command.
1653
1654 This method returns a new list to be used as a command."""
1655 new_command = command[:]
1656 if revision:
1657 new_command.extend(['--revision', str(revision).strip()])
maruel@chromium.org36ac2392011-10-12 16:36:11 +00001658 # We don't want interaction when jobs are used.
1659 if options.jobs > 1:
1660 new_command.append('--non-interactive')
tony@chromium.org99828122010-06-04 01:41:02 +00001661 # --force was added to 'svn update' in svn 1.5.
maruel@chromium.org36ac2392011-10-12 16:36:11 +00001662 # --accept was added to 'svn update' in svn 1.6.
1663 if not scm.SVN.AssertVersion('1.5')[0]:
1664 return new_command
1665
1666 # It's annoying to have it block in the middle of a sync, just sensible
1667 # defaults.
1668 if options.force:
tony@chromium.org99828122010-06-04 01:41:02 +00001669 new_command.append('--force')
maruel@chromium.org36ac2392011-10-12 16:36:11 +00001670 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]:
1671 new_command.extend(('--accept', 'theirs-conflict'))
1672 elif options.manually_grab_svn_rev:
1673 new_command.append('--force')
1674 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]:
1675 new_command.extend(('--accept', 'postpone'))
1676 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]:
1677 new_command.extend(('--accept', 'postpone'))
tony@chromium.org99828122010-06-04 01:41:02 +00001678 return new_command