blob: a5dfbfcebf9226c5b2b336e530caa91d761c5765 [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
John Budorick0f7b2002018-01-19 15:46:17 -08009import collections
10import contextlib
borenet@google.comb2256212014-05-07 20:57:28 +000011import errno
John Budorick0f7b2002018-01-19 15:46:17 -080012import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000013import logging
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000014import os
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000015import posixpath
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000016import re
maruel@chromium.org90541732011-04-01 17:54:18 +000017import sys
ilevy@chromium.org3534aa52013-07-20 01:58:08 +000018import tempfile
John Budorick0f7b2002018-01-19 15:46:17 -080019import threading
zty@chromium.org6279e8a2014-02-13 01:45:25 +000020import traceback
hinoka@google.com2f2ca142014-01-07 03:59:18 +000021import urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000022
hinoka@google.com2f2ca142014-01-07 03:59:18 +000023import download_from_google_storage
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000024import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +000025import git_cache
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000026import scm
borenet@google.comb2256212014-05-07 20:57:28 +000027import shutil
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000028import subprocess2
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000029
30
szager@chromium.org71cbb502013-04-19 23:30:15 +000031THIS_FILE_PATH = os.path.abspath(__file__)
32
hinoka@google.com2f2ca142014-01-07 03:59:18 +000033GSUTIL_DEFAULT_PATH = os.path.join(
hinoka@chromium.orgb091aa52014-12-20 01:47:31 +000034 os.path.dirname(os.path.abspath(__file__)), 'gsutil.py')
hinoka@google.com2f2ca142014-01-07 03:59:18 +000035
maruel@chromium.org79d62372015-06-01 18:50:55 +000036
smutae7ea312016-07-18 11:59:41 -070037class NoUsableRevError(gclient_utils.Error):
38 """Raised if requested revision isn't found in checkout."""
39
40
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000041class DiffFiltererWrapper(object):
42 """Simple base class which tracks which file is being diffed and
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000043 replaces instances of its file name in the original and
agable41e3a6c2016-10-20 11:36:56 -070044 working copy lines of the git diff output."""
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000045 index_string = None
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000046 original_prefix = "--- "
47 working_prefix = "+++ "
48
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000049 def __init__(self, relpath, print_func):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000050 # Note that we always use '/' as the path separator to be
agable41e3a6c2016-10-20 11:36:56 -070051 # consistent with cygwin-style output on Windows
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000052 self._relpath = relpath.replace("\\", "/")
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000053 self._current_file = None
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000054 self._print_func = print_func
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000055
maruel@chromium.org6e29d572010-06-04 17:32:20 +000056 def SetCurrentFile(self, current_file):
57 self._current_file = current_file
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000058
iannucci@chromium.org3830a672013-02-19 20:15:14 +000059 @property
60 def _replacement_file(self):
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000061 return posixpath.join(self._relpath, self._current_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000062
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000063 def _Replace(self, line):
64 return line.replace(self._current_file, self._replacement_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000065
66 def Filter(self, line):
67 if (line.startswith(self.index_string)):
68 self.SetCurrentFile(line[len(self.index_string):])
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000069 line = self._Replace(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000070 else:
71 if (line.startswith(self.original_prefix) or
72 line.startswith(self.working_prefix)):
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000073 line = self._Replace(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000074 self._print_func(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000075
76
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000077class GitDiffFilterer(DiffFiltererWrapper):
78 index_string = "diff --git "
79
80 def SetCurrentFile(self, current_file):
81 # Get filename by parsing "a/<filename> b/<filename>"
82 self._current_file = current_file[:(len(current_file)/2)][2:]
83
84 def _Replace(self, line):
85 return re.sub("[a|b]/" + self._current_file, self._replacement_file, line)
86
87
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000088# SCMWrapper base class
89
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000090class SCMWrapper(object):
91 """Add necessary glue between all the supported SCM.
92
msb@chromium.orgd6504212010-01-13 17:34:31 +000093 This is the abstraction layer to bind to different SCM.
94 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000095
96 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
Edward Lemur231f5ea2018-01-31 19:02:36 +010097 out_cb=None, print_outbuf=False):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000098 self.url = url
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +000099 self._root_dir = root_dir
100 if self._root_dir:
101 self._root_dir = self._root_dir.replace('/', os.sep)
102 self.relpath = relpath
103 if self.relpath:
104 self.relpath = self.relpath.replace('/', os.sep)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000105 if self.relpath and self._root_dir:
106 self.checkout_path = os.path.join(self._root_dir, self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000107 if out_fh is None:
108 out_fh = sys.stdout
109 self.out_fh = out_fh
110 self.out_cb = out_cb
Edward Lemur231f5ea2018-01-31 19:02:36 +0100111 self.print_outbuf = print_outbuf
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000112
113 def Print(self, *args, **kwargs):
114 kwargs.setdefault('file', self.out_fh)
115 if kwargs.pop('timestamp', True):
116 self.out_fh.write('[%s] ' % gclient_utils.Elapsed())
117 print(*args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000118
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000119 def RunCommand(self, command, options, args, file_list=None):
agabledebf6c82016-12-21 12:50:12 -0800120 commands = ['update', 'updatesingle', 'revert',
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000121 'revinfo', 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000122
123 if not command in commands:
124 raise gclient_utils.Error('Unknown command %s' % command)
125
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000126 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000127 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000128 command, self.__class__.__name__))
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000129
130 return getattr(self, command)(options, args, file_list)
131
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000132 @staticmethod
133 def _get_first_remote_url(checkout_path):
134 log = scm.GIT.Capture(
135 ['config', '--local', '--get-regexp', r'remote.*.url'],
136 cwd=checkout_path)
137 # Get the second token of the first line of the log.
138 return log.splitlines()[0].split(' ', 1)[1]
139
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000140 def GetCacheMirror(self):
141 if (getattr(self, 'cache_dir', None)):
142 url, _ = gclient_utils.SplitUrlRevision(self.url)
143 return git_cache.Mirror(url)
144 return None
145
smut@google.comd33eab32014-07-07 19:35:18 +0000146 def GetActualRemoteURL(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000147 """Attempt to determine the remote URL for this SCMWrapper."""
smut@google.comd33eab32014-07-07 19:35:18 +0000148 # Git
borenet@google.combda475e2014-03-24 19:04:45 +0000149 if os.path.exists(os.path.join(self.checkout_path, '.git')):
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000150 actual_remote_url = self._get_first_remote_url(self.checkout_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000151
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000152 mirror = self.GetCacheMirror()
153 # If the cache is used, obtain the actual remote URL from there.
154 if (mirror and mirror.exists() and
155 mirror.mirror_path.replace('\\', '/') ==
156 actual_remote_url.replace('\\', '/')):
157 actual_remote_url = self._get_first_remote_url(mirror.mirror_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000158 return actual_remote_url
borenet@google.com88d10082014-03-21 17:24:48 +0000159 return None
160
borenet@google.com4e9be262014-04-08 19:40:30 +0000161 def DoesRemoteURLMatch(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000162 """Determine whether the remote URL of this checkout is the expected URL."""
163 if not os.path.exists(self.checkout_path):
164 # A checkout which doesn't exist can't be broken.
165 return True
166
smut@google.comd33eab32014-07-07 19:35:18 +0000167 actual_remote_url = self.GetActualRemoteURL(options)
borenet@google.com88d10082014-03-21 17:24:48 +0000168 if actual_remote_url:
borenet@google.com8156c9f2014-04-01 16:41:36 +0000169 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
170 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
borenet@google.com88d10082014-03-21 17:24:48 +0000171 else:
172 # This may occur if the self.checkout_path exists but does not contain a
agable41e3a6c2016-10-20 11:36:56 -0700173 # valid git checkout.
borenet@google.com88d10082014-03-21 17:24:48 +0000174 return False
175
borenet@google.comb09097a2014-04-09 19:09:08 +0000176 def _DeleteOrMove(self, force):
177 """Delete the checkout directory or move it out of the way.
178
179 Args:
180 force: bool; if True, delete the directory. Otherwise, just move it.
181 """
borenet@google.comb2256212014-05-07 20:57:28 +0000182 if force and os.environ.get('CHROME_HEADLESS') == '1':
183 self.Print('_____ Conflicting directory found in %s. Removing.'
184 % self.checkout_path)
185 gclient_utils.AddWarning('Conflicting directory %s deleted.'
186 % self.checkout_path)
187 gclient_utils.rmtree(self.checkout_path)
188 else:
189 bad_scm_dir = os.path.join(self._root_dir, '_bad_scm',
190 os.path.dirname(self.relpath))
191
192 try:
193 os.makedirs(bad_scm_dir)
194 except OSError as e:
195 if e.errno != errno.EEXIST:
196 raise
197
198 dest_path = tempfile.mkdtemp(
199 prefix=os.path.basename(self.relpath),
200 dir=bad_scm_dir)
201 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
202 % (self.checkout_path, dest_path))
203 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
204 % (self.checkout_path, dest_path))
205 shutil.move(self.checkout_path, dest_path)
borenet@google.comb09097a2014-04-09 19:09:08 +0000206
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000207
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000208class GitWrapper(SCMWrapper):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000209 """Wrapper for Git"""
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000210 name = 'git'
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000211 remote = 'origin'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000212
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000213 cache_dir = None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000214
John Budorick0f7b2002018-01-19 15:46:17 -0800215 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000216 """Removes 'git+' fake prefix from git URL."""
217 if url.startswith('git+http://') or url.startswith('git+https://'):
218 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800219 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000220 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
221 if self.out_cb:
222 filter_kwargs['predicate'] = self.out_cb
223 self.filter = gclient_utils.GitFilter(**filter_kwargs)
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000224
mukai@chromium.org9e3e82c2012-04-18 12:55:43 +0000225 @staticmethod
226 def BinaryExists():
227 """Returns true if the command exists."""
228 try:
229 # We assume git is newer than 1.7. See: crbug.com/114483
230 result, version = scm.GIT.AssertVersion('1.7')
231 if not result:
232 raise gclient_utils.Error('Git version is older than 1.7: %s' % version)
233 return result
234 except OSError:
235 return False
236
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000237 def GetCheckoutRoot(self):
238 return scm.GIT.GetCheckoutRoot(self.checkout_path)
239
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000240 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000241 """Returns the given revision's date in ISO-8601 format (which contains the
242 time zone)."""
243 # TODO(floitsch): get the time-stamp of the given revision and not just the
244 # time-stamp of the currently checked out revision.
245 return self._Capture(['log', '-n', '1', '--format=%ai'])
246
Aaron Gablef4068aa2017-12-12 15:14:09 -0800247 def _GetDiffFilenames(self, base):
248 """Returns the names of files modified since base."""
249 return self._Capture(
250 # Filter to remove base if it is None.
251 filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only', base])
252 ).split()
253
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000254 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 15:45:56 -0800255 _, revision = gclient_utils.SplitUrlRevision(self.url)
256 if not revision:
257 revision = 'refs/remotes/%s/master' % self.remote
258 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000259
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000260 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000261 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000262 repository.
263
264 The patch file is generated from a diff of the merge base of HEAD and
265 its upstream branch.
266 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700267 try:
268 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
269 except subprocess2.CalledProcessError:
270 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000271 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700272 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000273 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000274 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000275
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800276 def _Scrub(self, target, options):
277 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000278 quiet = []
279 if not options.verbose:
280 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800281 self._Run(['reset', '--hard', target] + quiet, options)
282 if options.force and options.delete_unversioned_trees:
283 # where `target` is a commit that contains both upper and lower case
284 # versions of the same file on a case insensitive filesystem, we are
285 # actually in a broken state here. The index will have both 'a' and 'A',
286 # but only one of them will exist on the disk. To progress, we delete
287 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800288 output = self._Capture([
289 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800290 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800291 # --porcelain (v1) looks like:
292 # XY filename
293 try:
294 filename = line[3:]
295 self.Print('_____ Deleting residual after reset: %r.' % filename)
296 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800297 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800298 except OSError:
299 pass
300
301 def _FetchAndReset(self, revision, file_list, options):
302 """Equivalent to git fetch; git reset."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000303 self._UpdateBranchHeads(options, fetch=False)
304
dnj@chromium.org680f2172014-06-25 00:39:32 +0000305 self._Fetch(options, prune=True, quiet=options.verbose)
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800306 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000307 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800308 files = self._Capture(
309 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000310 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
311
szager@chromium.org8a139702014-06-20 15:55:01 +0000312 def _DisableHooks(self):
313 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
314 if not os.path.isdir(hook_dir):
315 return
316 for f in os.listdir(hook_dir):
317 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000318 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
319 if os.path.exists(disabled_hook_path):
320 os.remove(disabled_hook_path)
321 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000322
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000323 def _maybe_break_locks(self, options):
324 """This removes all .lock files from this repo's .git directory, if the
325 user passed the --break_repo_locks command line flag.
326
327 In particular, this will cleanup index.lock files, as well as ref lock
328 files.
329 """
330 if options.break_repo_locks:
331 git_dir = os.path.join(self.checkout_path, '.git')
332 for path, _, filenames in os.walk(git_dir):
333 for filename in filenames:
334 if filename.endswith('.lock'):
335 to_break = os.path.join(path, filename)
336 self.Print('breaking lock: %s' % (to_break,))
337 try:
338 os.remove(to_break)
339 except OSError as ex:
340 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
341 raise
342
Edward Lesmesc621b212018-03-21 20:26:56 -0400343 def apply_patch_ref(self, patch_repo, patch_ref, options, file_list):
344 base_rev = self._Capture(['rev-parse', 'HEAD'])
345 self.Print('===Applying patch ref===')
346 self.Print('Repo is %r @ %r, ref is %r, root is %r' % (
347 patch_repo, patch_ref, base_rev, self.checkout_path))
348 self._Capture(['reset', '--hard'])
349 self._Capture(['fetch', patch_repo, patch_ref])
350 file_list.extend(self._GetDiffFilenames('FETCH_HEAD'))
351 self._Capture(['checkout', 'FETCH_HEAD'])
352
353 if options.rebase_patch_ref:
354 try:
355 # TODO(ehmaldonado): Look into cherry-picking to avoid an expensive
356 # checkout + rebase.
357 self._Capture(['rebase', base_rev])
358 except subprocess2.CalledProcessError as e:
359 self._Capture(['rebase', '--abort'])
360 self.Print('Failed to apply %r @ %r to %r at %r' % (
361 patch_repo, patch_ref, base_rev, self.checkout_path))
362 self.Print('git returned non-zero exit status %s:\n%s' % (
363 e.returncode, e.stderr))
364 raise
365 if options.reset_patch_ref:
366 self._Capture(['reset', '--soft', base_rev])
367
msb@chromium.orge28e4982009-09-25 20:51:45 +0000368 def update(self, options, args, file_list):
369 """Runs git to update or transparently checkout the working copy.
370
371 All updated files will be appended to file_list.
372
373 Raises:
374 Error: if can't get URL for relative path.
375 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000376 if args:
377 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
378
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000379 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000380
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000381 # If a dependency is not pinned, track the default remote branch.
smut@google.comd33eab32014-07-07 19:35:18 +0000382 default_rev = 'refs/remotes/%s/master' % self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000383 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000384 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000385 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000386 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000387 # Override the revision number.
388 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000389 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000390 # Check again for a revision in case an initial ref was specified
391 # in the url, for example bla.git@refs/heads/custombranch
392 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000393 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000394 if not revision:
395 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000396
szager@chromium.org8a139702014-06-20 15:55:01 +0000397 if managed:
398 self._DisableHooks()
399
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000400 printed_path = False
401 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000402 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700403 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000404 verbose = ['--verbose']
405 printed_path = True
406
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000407 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
408 if remote_ref:
smut@google.comd33eab32014-07-07 19:35:18 +0000409 # Rewrite remote refs to their local equivalents.
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000410 revision = ''.join(remote_ref)
411 rev_type = "branch"
412 elif revision.startswith('refs/'):
413 # Local branch? We probably don't want to support, since DEPS should
414 # always specify branches as they are in the upstream repo.
smut@google.comd33eab32014-07-07 19:35:18 +0000415 rev_type = "branch"
416 else:
417 # hash is also a tag, only make a distinction at checkout
418 rev_type = "hash"
419
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000420 mirror = self._GetMirror(url, options)
421 if mirror:
422 url = mirror.mirror_path
423
primiano@chromium.org1c127382015-02-17 11:15:40 +0000424 # If we are going to introduce a new project, there is a possibility that
425 # we are syncing back to a state where the project was originally a
426 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
427 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
428 # In such case, we might have a backup of the former .git folder, which can
429 # be used to avoid re-fetching the entire repo again (useful for bisects).
430 backup_dir = self.GetGitBackupDirPath()
431 target_dir = os.path.join(self.checkout_path, '.git')
432 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
433 gclient_utils.safe_makedirs(self.checkout_path)
434 os.rename(backup_dir, target_dir)
435 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800436 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000437
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000438 if (not os.path.exists(self.checkout_path) or
439 (os.path.isdir(self.checkout_path) and
440 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000441 if mirror:
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800442 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000443 try:
smut@google.comd33eab32014-07-07 19:35:18 +0000444 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000445 except subprocess2.CalledProcessError:
446 self._DeleteOrMove(options.force)
smut@google.comd33eab32014-07-07 19:35:18 +0000447 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000448 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800449 files = self._Capture(
450 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000451 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000452 if not verbose:
453 # Make the output a little prettier. It's nice to have some whitespace
454 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000455 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000456 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000457
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000458 if not managed:
459 self._UpdateBranchHeads(options, fetch=False)
460 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
461 return self._Capture(['rev-parse', '--verify', 'HEAD'])
462
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000463 self._maybe_break_locks(options)
464
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000465 if mirror:
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800466 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000467
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000468 # See if the url has changed (the unittests use git://foo for the url, let
469 # that through).
470 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
471 return_early = False
472 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
473 # unit test pass. (and update the comment above)
474 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
475 # This allows devs to use experimental repos which have a different url
476 # but whose branch(s) are the same as official repos.
borenet@google.comb09097a2014-04-09 19:09:08 +0000477 if (current_url.rstrip('/') != url.rstrip('/') and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000478 url != 'git://foo' and
479 subprocess2.capture(
480 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
481 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000482 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000483 if not (options.force or options.reset):
484 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700485 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000486 # Switch over to the new upstream
487 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000488 if mirror:
489 with open(os.path.join(
490 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
491 'w') as fh:
492 fh.write(os.path.join(url, 'objects'))
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000493 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
smut@google.comd33eab32014-07-07 19:35:18 +0000494 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000495
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000496 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000497 else:
498 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000499
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000500 if return_early:
501 return self._Capture(['rev-parse', '--verify', 'HEAD'])
502
msb@chromium.org5bde4852009-12-14 16:47:12 +0000503 cur_branch = self._GetCurrentBranch()
504
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000505 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000506 # 0) HEAD is detached. Probably from our initial clone.
507 # - make sure HEAD is contained by a named ref, then update.
508 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700509 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000510 # - try to rebase onto the new hash or branch
511 # 2) current branch is tracking a remote branch with local committed
512 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000513 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000514 # 3) current branch is tracking a remote branch w/or w/out changes, and
515 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000516 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000517 # 4) current branch is tracking a remote branch, but DEPS switches to a
518 # different remote branch, and
519 # a) current branch has no local changes, and --force:
520 # - checkout new branch
521 # b) current branch has local changes, and --force and --reset:
522 # - checkout new branch
523 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000524
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000525 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
526 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000527 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
528 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000529 if cur_branch is None:
530 upstream_branch = None
531 current_type = "detached"
532 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000533 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000534 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
535 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
536 current_type = "hash"
537 logging.debug("Current branch is not tracking an upstream (remote)"
538 " branch.")
539 elif upstream_branch.startswith('refs/remotes'):
540 current_type = "branch"
541 else:
542 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000543
smut@google.comd33eab32014-07-07 19:35:18 +0000544 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000545 # Update the remotes first so we have all the refs.
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000546 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000547 cwd=self.checkout_path)
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000548 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000549 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000550
mmoss@chromium.org37ac0e32015-08-18 18:14:38 +0000551 self._UpdateBranchHeads(options, fetch=True)
mmoss@chromium.orge409df62013-04-16 17:28:57 +0000552
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200553 revision = self._AutoFetchRef(options, revision)
554
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000555 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000556 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000557 target = 'HEAD'
558 if options.upstream and upstream_branch:
559 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800560 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000561
msb@chromium.org786fb682010-06-02 15:16:23 +0000562 if current_type == 'detached':
563 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800564 # We just did a Scrub, this is as clean as it's going to get. In
565 # particular if HEAD is a commit that contains two versions of the same
566 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
567 # to actually "Clean" the checkout; that commit is uncheckoutable on this
568 # system. The best we can do is carry forward to the checkout step.
569 if not (options.force or options.reset):
570 self._CheckClean(revision)
agable83faed02016-10-24 14:37:10 -0700571 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000572 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000573 self.Print('Up-to-date; skipping checkout.')
574 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000575 # 'git checkout' may need to overwrite existing untracked files. Allow
576 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000577 self._Checkout(
578 options,
579 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000580 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000581 quiet=True,
582 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000583 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700584 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000585 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000586 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700587 # Can't find a merge-base since we don't know our upstream. That makes
588 # this command VERY likely to produce a rebase failure. For now we
589 # assume origin is our upstream since that's what the old behavior was.
590 upstream_branch = self.remote
591 if options.revision or deps_revision:
592 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700593 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700594 printed_path=printed_path, merge=options.merge)
595 printed_path = True
smut@google.comd33eab32014-07-07 19:35:18 +0000596 elif rev_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000597 # case 2
agable1a8439a2016-10-24 16:36:14 -0700598 self._AttemptRebase(upstream_branch, file_list, options,
smut@google.comd33eab32014-07-07 19:35:18 +0000599 newbase=revision, printed_path=printed_path,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000600 merge=options.merge)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000601 printed_path = True
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000602 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000603 # case 4
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000604 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000605 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700606 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000607 switch_error = ("Could not switch upstream branch from %s to %s\n"
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000608 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000609 "Please use --force or merge or rebase manually:\n" +
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000610 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
611 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000612 force_switch = False
613 if options.force:
614 try:
agable83faed02016-10-24 14:37:10 -0700615 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000616 # case 4a
617 force_switch = True
618 except gclient_utils.Error as e:
619 if options.reset:
620 # case 4b
621 force_switch = True
622 else:
623 switch_error = '%s\n%s' % (e.message, switch_error)
624 if force_switch:
625 self.Print("Switching upstream branch from %s to %s" %
626 (upstream_branch, new_base))
627 switch_branch = 'gclient_' + remote_ref[1]
628 self._Capture(['branch', '-f', switch_branch, new_base])
629 self._Checkout(options, switch_branch, force=True, quiet=True)
630 else:
631 # case 4c
632 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000633 else:
634 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800635 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000636 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000637 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000638 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000639 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000640 if options.merge:
641 merge_args.append('--ff')
642 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000643 merge_args.append('--ff-only')
644 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000645 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000646 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700647 rebase_files = []
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000648 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
649 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700650 self.Print('_____ %s at %s' % (self.relpath, revision),
651 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000652 printed_path = True
653 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000654 if not options.auto_rebase:
655 try:
656 action = self._AskForData(
657 'Cannot %s, attempt to rebase? '
658 '(y)es / (q)uit / (s)kip : ' %
659 ('merge' if options.merge else 'fast-forward merge'),
660 options)
661 except ValueError:
662 raise gclient_utils.Error('Invalid Character')
663 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700664 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000665 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000666 printed_path = True
667 break
668 elif re.match(r'quit|q', action, re.I):
669 raise gclient_utils.Error("Can't fast-forward, please merge or "
670 "rebase manually.\n"
671 "cd %s && git " % self.checkout_path
672 + "rebase %s" % upstream_branch)
673 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000674 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000675 return
676 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000677 self.Print('Input not recognized')
smut@google.com27c9c8a2014-09-11 19:57:55 +0000678 elif re.match("error: Your local changes to '.*' would be "
679 "overwritten by merge. Aborting.\nPlease, commit your "
680 "changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000681 e.stderr):
682 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700683 self.Print('_____ %s at %s' % (self.relpath, revision),
684 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000685 printed_path = True
686 raise gclient_utils.Error(e.stderr)
687 else:
688 # Some other problem happened with the merge
689 logging.error("Error during fast-forward merge in %s!" % self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000690 self.Print(e.stderr)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000691 raise
692 else:
693 # Fast-forward merge was successful
694 if not re.match('Already up-to-date.', merge_output) or verbose:
695 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700696 self.Print('_____ %s at %s' % (self.relpath, revision),
697 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000698 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000699 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000700 if not verbose:
701 # Make the output a little prettier. It's nice to have some
702 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000703 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000704
agablec3937b92016-10-25 10:13:03 -0700705 if file_list is not None:
706 file_list.extend(
707 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000708
709 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000710 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700711 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000712 '\nConflict while rebasing this branch.\n'
713 'Fix the conflict and run gclient again.\n'
714 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700715 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000716
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000717 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000718 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
719 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000720
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000721 # If --reset and --delete_unversioned_trees are specified, remove any
722 # untracked directories.
723 if options.reset and options.delete_unversioned_trees:
724 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
725 # merge-base by default), so doesn't include untracked files. So we use
726 # 'git ls-files --directory --others --exclude-standard' here directly.
727 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800728 ['-c', 'core.quotePath=false', 'ls-files',
729 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000730 self.checkout_path)
731 for path in (p for p in paths.splitlines() if p.endswith('/')):
732 full_path = os.path.join(self.checkout_path, path)
733 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000734 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000735 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000736
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000737 return self._Capture(['rev-parse', '--verify', 'HEAD'])
738
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000739 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000740 """Reverts local modifications.
741
742 All reverted files will be appended to file_list.
743 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000744 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000745 # revert won't work if the directory doesn't exist. It needs to
746 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000747 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000748 # Don't reuse the args.
749 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000750
751 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000752 if options.upstream:
753 if self._GetCurrentBranch():
754 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
755 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000756 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000757 if not deps_revision:
758 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000759 if deps_revision.startswith('refs/heads/'):
760 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700761 try:
762 deps_revision = self.GetUsableRev(deps_revision, options)
763 except NoUsableRevError as e:
764 # If the DEPS entry's url and hash changed, try to update the origin.
765 # See also http://crbug.com/520067.
766 logging.warn(
767 'Couldn\'t find usable revision, will retrying to update instead: %s',
768 e.message)
769 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000770
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000771 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800772 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000773
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800774 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000775 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000776
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000777 if file_list is not None:
778 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
779
780 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000781 """Returns revision"""
782 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000783
msb@chromium.orge28e4982009-09-25 20:51:45 +0000784 def runhooks(self, options, args, file_list):
785 self.status(options, args, file_list)
786
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000787 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000788 """Display status information."""
789 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000790 self.Print('________ couldn\'t run status in %s:\n'
791 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000792 else:
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700793 try:
794 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
795 except subprocess2.CalledProcessError:
796 merge_base = []
Aaron Gablef4068aa2017-12-12 15:14:09 -0800797 self._Run(
798 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
799 options, stdout=self.out_fh, always=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000800 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800801 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000802 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000803
smutae7ea312016-07-18 11:59:41 -0700804 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700805 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700806 sha1 = None
807 if not os.path.isdir(self.checkout_path):
808 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800809 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700810
811 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
812 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700813 else:
agable41e3a6c2016-10-20 11:36:56 -0700814 # May exist in origin, but we don't have it yet, so fetch and look
815 # again.
816 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700817 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
818 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700819
820 if not sha1:
821 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800822 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700823
824 return sha1
825
msb@chromium.orge6f78352010-01-13 17:05:33 +0000826 def FullUrlForRelativeUrl(self, url):
827 # Strip from last '/'
828 # Equivalent to unix basename
829 base_url = self.url
830 return base_url[:base_url.rfind('/')] + url
831
primiano@chromium.org1c127382015-02-17 11:15:40 +0000832 def GetGitBackupDirPath(self):
833 """Returns the path where the .git folder for the current project can be
834 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
835 return os.path.join(self._root_dir,
836 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
837
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000838 def _GetMirror(self, url, options):
839 """Get a git_cache.Mirror object for the argument url."""
840 if not git_cache.Mirror.GetCachePath():
841 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000842 mirror_kwargs = {
843 'print_func': self.filter,
844 'refs': []
845 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000846 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
847 mirror_kwargs['refs'].append('refs/branch-heads/*')
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000848 if hasattr(options, 'with_tags') and options.with_tags:
849 mirror_kwargs['refs'].append('refs/tags/*')
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000850 return git_cache.Mirror(url, **mirror_kwargs)
851
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800852 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
853 """Update a git mirror by fetching the latest commits from the remote,
854 unless mirror already contains revision whose type is sha1 hash.
855 """
856 if rev_type == 'hash' and mirror.contains_revision(revision):
857 if options.verbose:
858 self.Print('skipping mirror update, it has rev=%s already' % revision,
859 timestamp=False)
860 return
861
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000862 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000863 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000864 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000865 depth = 10
866 else:
867 depth = 10000
868 else:
869 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000870 mirror.populate(verbose=options.verbose,
871 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000872 depth=depth,
873 ignore_lock=getattr(options, 'ignore_locks', False),
874 lock_timeout=getattr(options, 'lock_timeout', 0))
875 mirror.unlock()
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000876
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000877 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000878 """Clone a git repository from the given URL.
879
msb@chromium.org786fb682010-06-02 15:16:23 +0000880 Once we've cloned the repo, we checkout a working branch if the specified
881 revision is a branch head. If it is a tag or a specific commit, then we
882 leave HEAD detached as it makes future updates simpler -- in this case the
883 user should first create a new branch or switch to an existing branch before
884 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000885 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000886 # git clone doesn't seem to insert a newline properly before printing
887 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000888 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +0000889 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +0000890 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000891 if self.cache_dir:
892 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000893 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000894 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000895 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +0000896 # If the parent directory does not exist, Git clone on Windows will not
897 # create it, so we need to do it manually.
898 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000899 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000900
901 template_dir = None
902 if hasattr(options, 'no_history') and options.no_history:
903 if gclient_utils.IsGitSha(revision):
904 # In the case of a subproject, the pinned sha is not necessarily the
905 # head of the remote branch (so we can't just use --depth=N). Instead,
906 # we tell git to fetch all the remote objects from SHA..HEAD by means of
907 # a template git dir which has a 'shallow' file pointing to the sha.
908 template_dir = tempfile.mkdtemp(
909 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
910 dir=parent_dir)
911 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
912 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
913 template_file.write(revision)
914 clone_cmd.append('--template=' + template_dir)
915 else:
916 # Otherwise, we're just interested in the HEAD. Just use --depth.
917 clone_cmd.append('--depth=1')
918
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000919 tmp_dir = tempfile.mkdtemp(
920 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
921 dir=parent_dir)
922 try:
923 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +0100924 if self.print_outbuf:
925 print_stdout = True
926 stdout = gclient_utils.WriteToStdout(self.out_fh)
927 else:
928 print_stdout = False
929 stdout = self.out_fh
930 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
931 print_stdout=print_stdout, stdout=stdout)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000932 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000933 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
934 os.path.join(self.checkout_path, '.git'))
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000935 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000936 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000937 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000938 finally:
939 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000940 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000941 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000942 if template_dir:
943 gclient_utils.rmtree(template_dir)
mmoss@chromium.org1a6bec02014-06-02 21:53:29 +0000944 self._UpdateBranchHeads(options, fetch=True)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200945 revision = self._AutoFetchRef(options, revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000946 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
947 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000948 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +0000949 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000950 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +0000951 ('Checked out %s to a detached HEAD. Before making any commits\n'
952 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
953 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
954 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000955
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000956 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000957 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000958 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000959 raise gclient_utils.Error("Background task requires input. Rerun "
960 "gclient with --jobs=1 so that\n"
961 "interaction is possible.")
962 try:
963 return raw_input(prompt)
964 except KeyboardInterrupt:
965 # Hide the exception.
966 sys.exit(1)
967
968
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000969 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000970 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000971 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000972 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800973 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000974 revision = upstream
975 if newbase:
976 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000977 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000978 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000979 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000980 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000981 printed_path = True
982 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000983 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000984
985 if merge:
986 merge_output = self._Capture(['merge', revision])
987 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000988 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000989 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000990
991 # Build the rebase command here using the args
992 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
993 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000994 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000995 rebase_cmd.append('--verbose')
996 if newbase:
997 rebase_cmd.extend(['--onto', newbase])
998 rebase_cmd.append(upstream)
999 if branch:
1000 rebase_cmd.append(branch)
1001
1002 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001003 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001004 except subprocess2.CalledProcessError, e:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001005 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
1006 re.match(r'cannot rebase: your index contains uncommitted changes',
1007 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001008 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001009 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001010 'Cannot rebase because of unstaged changes.\n'
1011 '\'git reset --hard HEAD\' ?\n'
1012 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001013 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001014 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001015 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001016 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001017 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001018 break
1019 elif re.match(r'quit|q', rebase_action, re.I):
1020 raise gclient_utils.Error("Please merge or rebase manually\n"
1021 "cd %s && git " % self.checkout_path
1022 + "%s" % ' '.join(rebase_cmd))
1023 elif re.match(r'show|s', rebase_action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001024 self.Print('%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001025 continue
1026 else:
1027 gclient_utils.Error("Input not recognized")
1028 continue
1029 elif re.search(r'^CONFLICT', e.stdout, re.M):
1030 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1031 "Fix the conflict and run gclient again.\n"
1032 "See 'man git-rebase' for details.\n")
1033 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001034 self.Print(e.stdout.strip())
1035 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001036 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1037 "manually.\ncd %s && git " %
1038 self.checkout_path
1039 + "%s" % ' '.join(rebase_cmd))
1040
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001041 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001042 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001043 # Make the output a little prettier. It's nice to have some
1044 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001045 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001046
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001047 @staticmethod
1048 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001049 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1050 if not ok:
1051 raise gclient_utils.Error('git version %s < minimum required %s' %
1052 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001053
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001054 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
1055 # Special case handling if all 3 conditions are met:
1056 # * the mirros have recently changed, but deps destination remains same,
1057 # * the git histories of mirrors are conflicting.
1058 # * git cache is used
1059 # This manifests itself in current checkout having invalid HEAD commit on
1060 # most git operations. Since git cache is used, just deleted the .git
1061 # folder, and re-create it by cloning.
1062 try:
1063 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1064 except subprocess2.CalledProcessError as e:
1065 if ('fatal: bad object HEAD' in e.stderr
1066 and self.cache_dir and self.cache_dir in url):
1067 self.Print((
1068 'Likely due to DEPS change with git cache_dir, '
1069 'the current commit points to no longer existing object.\n'
1070 '%s' % e)
1071 )
1072 self._DeleteOrMove(options.force)
1073 self._Clone(revision, url, options)
1074 else:
1075 raise
1076
msb@chromium.org786fb682010-06-02 15:16:23 +00001077 def _IsRebasing(self):
1078 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1079 # have a plumbing command to determine whether a rebase is in progress, so
1080 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1081 g = os.path.join(self.checkout_path, '.git')
1082 return (
1083 os.path.isdir(os.path.join(g, "rebase-merge")) or
1084 os.path.isdir(os.path.join(g, "rebase-apply")))
1085
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001086 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001087 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1088 if os.path.exists(lockfile):
1089 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001090 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001091 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1092 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001093 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001094
msb@chromium.org786fb682010-06-02 15:16:23 +00001095 # Make sure the tree is clean; see git-rebase.sh for reference
1096 try:
1097 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001098 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001099 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001100 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001101 '\tYou have unstaged changes.\n'
1102 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001103 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001104 try:
1105 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001106 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001107 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001108 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001109 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001110 '\tYour index contains uncommitted changes\n'
1111 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001112 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001113
agable83faed02016-10-24 14:37:10 -07001114 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001115 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1116 # reference by a commit). If not, error out -- most likely a rebase is
1117 # in progress, try to detect so we can give a better error.
1118 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001119 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1120 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001121 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001122 # Commit is not contained by any rev. See if the user is rebasing:
1123 if self._IsRebasing():
1124 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001125 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001126 '\tAlready in a conflict, i.e. (no branch).\n'
1127 '\tFix the conflict and run gclient again.\n'
1128 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1129 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001130 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001131 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001132 name = ('saved-by-gclient-' +
1133 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001134 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001135 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001136 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001137
msb@chromium.org5bde4852009-12-14 16:47:12 +00001138 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001139 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001140 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001141 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001142 return None
1143 return branch
1144
borenet@google.comc3e09d22014-04-10 13:58:18 +00001145 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001146 kwargs.setdefault('cwd', self.checkout_path)
1147 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001148 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001149 env = scm.GIT.ApplyEnvVars(kwargs)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001150 ret = subprocess2.check_output(['git'] + args, env=env, **kwargs)
1151 if strip:
1152 ret = ret.strip()
1153 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001154
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001155 def _Checkout(self, options, ref, force=False, quiet=None):
1156 """Performs a 'git-checkout' operation.
1157
1158 Args:
1159 options: The configured option set
1160 ref: (str) The branch/commit to checkout
1161 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1162 'None', the behavior is inferred from 'options.verbose'.
1163 Returns: (str) The output of the checkout operation
1164 """
1165 if quiet is None:
1166 quiet = (not options.verbose)
1167 checkout_args = ['checkout']
1168 if force:
1169 checkout_args.append('--force')
1170 if quiet:
1171 checkout_args.append('--quiet')
1172 checkout_args.append(ref)
1173 return self._Capture(checkout_args)
1174
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001175 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1176 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001177 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
1178 fetch_cmd = cfg + [
1179 'fetch',
1180 remote or self.remote,
1181 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001182 if refspec:
1183 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001184
1185 if prune:
1186 fetch_cmd.append('--prune')
1187 if options.verbose:
1188 fetch_cmd.append('--verbose')
1189 elif quiet:
1190 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001191 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001192
1193 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1194 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1195
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001196 def _UpdateBranchHeads(self, options, fetch=False):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001197 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1198 if requested."""
1199 need_fetch = fetch
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001200 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001201 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001202 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1203 '^\\+refs/branch-heads/\\*:.*$']
1204 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001205 need_fetch = True
1206 if hasattr(options, 'with_tags') and options.with_tags:
1207 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1208 '+refs/tags/*:refs/tags/*',
1209 '^\\+refs/tags/\\*:.*$']
1210 self._Run(config_cmd, options)
1211 need_fetch = True
1212 if fetch and need_fetch:
bpastene2a3e9912016-09-07 16:22:25 -07001213 self._Fetch(options, prune=options.force)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001214
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001215 def _AutoFetchRef(self, options, revision):
1216 """Attempts to fetch |revision| if not available in local repo.
1217
1218 Returns possibly updated revision."""
1219 try:
1220 self._Capture(['rev-parse', revision])
1221 except subprocess2.CalledProcessError:
1222 self._Fetch(options, refspec=revision)
1223 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1224 return revision
1225
dnj@chromium.org680f2172014-06-25 00:39:32 +00001226 def _Run(self, args, options, show_header=True, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001227 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001228 kwargs.setdefault('cwd', self.checkout_path)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001229 kwargs.setdefault('stdout', self.out_fh)
szager@chromium.org848fd492014-04-09 19:06:44 +00001230 kwargs['filter_fn'] = self.filter
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001231 kwargs.setdefault('print_stdout', False)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001232 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001233 cmd = ['git'] + args
dnj@chromium.org680f2172014-06-25 00:39:32 +00001234 if show_header:
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001235 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1236 else:
1237 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001238
1239
1240class CipdPackage(object):
1241 """A representation of a single CIPD package."""
1242
John Budorickd3ba72b2018-03-20 12:27:42 -07001243 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001244 self._authority_for_subdir = authority_for_subdir
1245 self._name = name
1246 self._version = version
1247
1248 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001249 def authority_for_subdir(self):
1250 """Whether this package has authority to act on behalf of its subdir.
1251
1252 Some operations should only be performed once per subdirectory. A package
1253 that has authority for its subdirectory is the only package that should
1254 perform such operations.
1255
1256 Returns:
1257 bool; whether this package has subdir authority.
1258 """
1259 return self._authority_for_subdir
1260
1261 @property
1262 def name(self):
1263 return self._name
1264
1265 @property
1266 def version(self):
1267 return self._version
1268
1269
1270class CipdRoot(object):
1271 """A representation of a single CIPD root."""
1272 def __init__(self, root_dir, service_url):
1273 self._all_packages = set()
1274 self._mutator_lock = threading.Lock()
1275 self._packages_by_subdir = collections.defaultdict(list)
1276 self._root_dir = root_dir
1277 self._service_url = service_url
1278
1279 def add_package(self, subdir, package, version):
1280 """Adds a package to this CIPD root.
1281
1282 As far as clients are concerned, this grants both root and subdir authority
1283 to packages arbitrarily. (The implementation grants root authority to the
1284 first package added and subdir authority to the first package added for that
1285 subdir, but clients should not depend on or expect that behavior.)
1286
1287 Args:
1288 subdir: str; relative path to where the package should be installed from
1289 the cipd root directory.
1290 package: str; the cipd package name.
1291 version: str; the cipd package version.
1292 Returns:
1293 CipdPackage; the package that was created and added to this root.
1294 """
1295 with self._mutator_lock:
1296 cipd_package = CipdPackage(
1297 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001298 not self._packages_by_subdir[subdir])
1299 self._all_packages.add(cipd_package)
1300 self._packages_by_subdir[subdir].append(cipd_package)
1301 return cipd_package
1302
1303 def packages(self, subdir):
1304 """Get the list of configured packages for the given subdir."""
1305 return list(self._packages_by_subdir[subdir])
1306
1307 def clobber(self):
1308 """Remove the .cipd directory.
1309
1310 This is useful for forcing ensure to redownload and reinitialize all
1311 packages.
1312 """
1313 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001314 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001315 try:
1316 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1317 except OSError:
1318 if os.path.exists(cipd_cache_dir):
1319 raise
1320
1321 @contextlib.contextmanager
1322 def _create_ensure_file(self):
1323 try:
1324 ensure_file = None
1325 with tempfile.NamedTemporaryFile(
1326 suffix='.ensure', delete=False) as ensure_file:
1327 for subdir, packages in sorted(self._packages_by_subdir.iteritems()):
1328 ensure_file.write('@Subdir %s\n' % subdir)
1329 for package in packages:
1330 ensure_file.write('%s %s\n' % (package.name, package.version))
1331 ensure_file.write('\n')
1332 yield ensure_file.name
1333 finally:
1334 if ensure_file is not None and os.path.exists(ensure_file.name):
1335 os.remove(ensure_file.name)
1336
1337 def ensure(self):
1338 """Run `cipd ensure`."""
1339 with self._mutator_lock:
1340 with self._create_ensure_file() as ensure_file:
1341 cmd = [
1342 'cipd', 'ensure',
1343 '-log-level', 'error',
1344 '-root', self.root_dir,
1345 '-ensure-file', ensure_file,
1346 ]
1347 gclient_utils.CheckCallAndFilterAndHeader(cmd)
1348
John Budorickd3ba72b2018-03-20 12:27:42 -07001349 def run(self, command):
1350 if command == 'update':
1351 self.ensure()
1352 elif command == 'revert':
1353 self.clobber()
1354 self.ensure()
1355
John Budorick0f7b2002018-01-19 15:46:17 -08001356 def created_package(self, package):
1357 """Checks whether this root created the given package.
1358
1359 Args:
1360 package: CipdPackage; the package to check.
1361 Returns:
1362 bool; whether this root created the given package.
1363 """
1364 return package in self._all_packages
1365
1366 @property
1367 def root_dir(self):
1368 return self._root_dir
1369
1370 @property
1371 def service_url(self):
1372 return self._service_url
1373
1374
1375class CipdWrapper(SCMWrapper):
1376 """Wrapper for CIPD.
1377
1378 Currently only supports chrome-infra-packages.appspot.com.
1379 """
John Budorick3929e9e2018-02-04 18:18:07 -08001380 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001381
1382 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1383 out_cb=None, root=None, package=None):
1384 super(CipdWrapper, self).__init__(
1385 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1386 out_cb=out_cb)
1387 assert root.created_package(package)
1388 self._package = package
1389 self._root = root
1390
1391 #override
1392 def GetCacheMirror(self):
1393 return None
1394
1395 #override
1396 def GetActualRemoteURL(self, options):
1397 return self._root.service_url
1398
1399 #override
1400 def DoesRemoteURLMatch(self, options):
1401 del options
1402 return True
1403
1404 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001405 """Does nothing.
1406
1407 CIPD packages should be reverted at the root by running
1408 `CipdRoot.run('revert')`.
1409 """
1410 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001411
1412 def diff(self, options, args, file_list):
1413 """CIPD has no notion of diffing."""
1414 pass
1415
1416 def pack(self, options, args, file_list):
1417 """CIPD has no notion of diffing."""
1418 pass
1419
1420 def revinfo(self, options, args, file_list):
1421 """Grab the instance ID."""
1422 try:
1423 tmpdir = tempfile.mkdtemp()
1424 describe_json_path = os.path.join(tmpdir, 'describe.json')
1425 cmd = [
1426 'cipd', 'describe',
1427 self._package.name,
1428 '-log-level', 'error',
1429 '-version', self._package.version,
1430 '-json-output', describe_json_path
1431 ]
1432 gclient_utils.CheckCallAndFilter(
1433 cmd, filter_fn=lambda _line: None, print_stdout=False)
1434 with open(describe_json_path) as f:
1435 describe_json = json.load(f)
1436 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1437 finally:
1438 gclient_utils.rmtree(tmpdir)
1439
1440 def status(self, options, args, file_list):
1441 pass
1442
1443 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001444 """Does nothing.
1445
1446 CIPD packages should be updated at the root by running
1447 `CipdRoot.run('update')`.
1448 """
1449 pass