blob: f2a5688ac814b2593e0aef9cc455a3da3e3cab32 [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):
Robert Iannuccia19649b2018-06-29 16:31:45 +0000141 if getattr(self, 'cache_dir', None):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000142 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
Robert Iannuccia19649b2018-06-29 16:31:45 +0000213 @property
214 def cache_dir(self):
215 try:
216 return git_cache.Mirror.GetCachePath()
217 except RuntimeError:
218 return None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000219
John Budorick0f7b2002018-01-19 15:46:17 -0800220 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000221 """Removes 'git+' fake prefix from git URL."""
222 if url.startswith('git+http://') or url.startswith('git+https://'):
223 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800224 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000225 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
226 if self.out_cb:
227 filter_kwargs['predicate'] = self.out_cb
228 self.filter = gclient_utils.GitFilter(**filter_kwargs)
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000229
mukai@chromium.org9e3e82c2012-04-18 12:55:43 +0000230 @staticmethod
231 def BinaryExists():
232 """Returns true if the command exists."""
233 try:
234 # We assume git is newer than 1.7. See: crbug.com/114483
235 result, version = scm.GIT.AssertVersion('1.7')
236 if not result:
237 raise gclient_utils.Error('Git version is older than 1.7: %s' % version)
238 return result
239 except OSError:
240 return False
241
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000242 def GetCheckoutRoot(self):
243 return scm.GIT.GetCheckoutRoot(self.checkout_path)
244
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000245 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000246 """Returns the given revision's date in ISO-8601 format (which contains the
247 time zone)."""
248 # TODO(floitsch): get the time-stamp of the given revision and not just the
249 # time-stamp of the currently checked out revision.
250 return self._Capture(['log', '-n', '1', '--format=%ai'])
251
Aaron Gablef4068aa2017-12-12 15:14:09 -0800252 def _GetDiffFilenames(self, base):
253 """Returns the names of files modified since base."""
254 return self._Capture(
255 # Filter to remove base if it is None.
256 filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only', base])
257 ).split()
258
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000259 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 15:45:56 -0800260 _, revision = gclient_utils.SplitUrlRevision(self.url)
261 if not revision:
262 revision = 'refs/remotes/%s/master' % self.remote
263 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000264
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000265 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000266 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000267 repository.
268
269 The patch file is generated from a diff of the merge base of HEAD and
270 its upstream branch.
271 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700272 try:
273 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
274 except subprocess2.CalledProcessError:
275 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000276 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700277 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000278 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000279 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000280
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800281 def _Scrub(self, target, options):
282 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000283 quiet = []
284 if not options.verbose:
285 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800286 self._Run(['reset', '--hard', target] + quiet, options)
287 if options.force and options.delete_unversioned_trees:
288 # where `target` is a commit that contains both upper and lower case
289 # versions of the same file on a case insensitive filesystem, we are
290 # actually in a broken state here. The index will have both 'a' and 'A',
291 # but only one of them will exist on the disk. To progress, we delete
292 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800293 output = self._Capture([
294 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800295 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800296 # --porcelain (v1) looks like:
297 # XY filename
298 try:
299 filename = line[3:]
300 self.Print('_____ Deleting residual after reset: %r.' % filename)
301 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800302 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800303 except OSError:
304 pass
305
John Budorick882c91e2018-07-12 22:11:41 +0000306 def _FetchAndReset(self, revision, file_list, options):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800307 """Equivalent to git fetch; git reset."""
Edward Lemur579c9862018-07-13 23:17:51 +0000308 self._SetFetchConfig(options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000309
dnj@chromium.org680f2172014-06-25 00:39:32 +0000310 self._Fetch(options, prune=True, quiet=options.verbose)
John Budorick882c91e2018-07-12 22:11:41 +0000311 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000312 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800313 files = self._Capture(
314 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000315 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
316
szager@chromium.org8a139702014-06-20 15:55:01 +0000317 def _DisableHooks(self):
318 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
319 if not os.path.isdir(hook_dir):
320 return
321 for f in os.listdir(hook_dir):
322 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000323 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
324 if os.path.exists(disabled_hook_path):
325 os.remove(disabled_hook_path)
326 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000327
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000328 def _maybe_break_locks(self, options):
329 """This removes all .lock files from this repo's .git directory, if the
330 user passed the --break_repo_locks command line flag.
331
332 In particular, this will cleanup index.lock files, as well as ref lock
333 files.
334 """
335 if options.break_repo_locks:
336 git_dir = os.path.join(self.checkout_path, '.git')
337 for path, _, filenames in os.walk(git_dir):
338 for filename in filenames:
339 if filename.endswith('.lock'):
340 to_break = os.path.join(path, filename)
341 self.Print('breaking lock: %s' % (to_break,))
342 try:
343 os.remove(to_break)
344 except OSError as ex:
345 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
346 raise
347
Edward Lesmesc621b212018-03-21 20:26:56 -0400348 def apply_patch_ref(self, patch_repo, patch_ref, options, file_list):
349 base_rev = self._Capture(['rev-parse', 'HEAD'])
350 self.Print('===Applying patch ref===')
Andrii Shyshkalov690d8d42018-06-14 22:57:17 +0000351 self.Print('Repo is %r @ %r, ref is %r, root is %r' % (
352 patch_repo, patch_ref, base_rev, self.checkout_path))
Edward Lesmesc621b212018-03-21 20:26:56 -0400353 self._Capture(['reset', '--hard'])
354 self._Capture(['fetch', patch_repo, patch_ref])
Andrii Shyshkalov690d8d42018-06-14 22:57:17 +0000355 if file_list is not None:
356 file_list.extend(self._GetDiffFilenames('FETCH_HEAD'))
357 self._Capture(['checkout', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400358
Andrii Shyshkalov690d8d42018-06-14 22:57:17 +0000359 if options.rebase_patch_ref:
360 try:
361 # TODO(ehmaldonado): Look into cherry-picking to avoid an expensive
362 # checkout + rebase.
363 self._Capture(['rebase', base_rev])
364 except subprocess2.CalledProcessError as e:
365 self.Print('Failed to apply %r @ %r to %r at %r' % (
366 patch_repo, patch_ref, base_rev, self.checkout_path))
367 self.Print('git returned non-zero exit status %s:\n%s' % (
368 e.returncode, e.stderr))
369 self._Capture(['rebase', '--abort'])
370 raise
Edward Lesmesc621b212018-03-21 20:26:56 -0400371 if options.reset_patch_ref:
372 self._Capture(['reset', '--soft', base_rev])
373
msb@chromium.orge28e4982009-09-25 20:51:45 +0000374 def update(self, options, args, file_list):
375 """Runs git to update or transparently checkout the working copy.
376
377 All updated files will be appended to file_list.
378
379 Raises:
380 Error: if can't get URL for relative path.
381 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000382 if args:
383 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
384
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000385 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000386
John Budorick882c91e2018-07-12 22:11:41 +0000387 # If a dependency is not pinned, track the default remote branch.
388 default_rev = 'refs/remotes/%s/master' % self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000389 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000390 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000391 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000392 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000393 # Override the revision number.
394 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000395 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000396 # Check again for a revision in case an initial ref was specified
397 # in the url, for example bla.git@refs/heads/custombranch
398 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000399 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000400 if not revision:
401 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000402
szager@chromium.org8a139702014-06-20 15:55:01 +0000403 if managed:
404 self._DisableHooks()
405
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000406 printed_path = False
407 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000408 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700409 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000410 verbose = ['--verbose']
411 printed_path = True
412
John Budorick882c91e2018-07-12 22:11:41 +0000413 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
414 if remote_ref:
415 # Rewrite remote refs to their local equivalents.
416 revision = ''.join(remote_ref)
417 rev_type = "branch"
418 elif revision.startswith('refs/'):
419 # Local branch? We probably don't want to support, since DEPS should
420 # always specify branches as they are in the upstream repo.
421 rev_type = "branch"
422 else:
423 # hash is also a tag, only make a distinction at checkout
424 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000425
John Budorick882c91e2018-07-12 22:11:41 +0000426 mirror = self._GetMirror(url, options)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000427 if mirror:
428 url = mirror.mirror_path
429
primiano@chromium.org1c127382015-02-17 11:15:40 +0000430 # If we are going to introduce a new project, there is a possibility that
431 # we are syncing back to a state where the project was originally a
432 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
433 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
434 # In such case, we might have a backup of the former .git folder, which can
435 # be used to avoid re-fetching the entire repo again (useful for bisects).
436 backup_dir = self.GetGitBackupDirPath()
437 target_dir = os.path.join(self.checkout_path, '.git')
438 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
439 gclient_utils.safe_makedirs(self.checkout_path)
440 os.rename(backup_dir, target_dir)
441 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800442 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000443
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000444 if (not os.path.exists(self.checkout_path) or
445 (os.path.isdir(self.checkout_path) and
446 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000447 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000448 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000449 try:
John Budorick882c91e2018-07-12 22:11:41 +0000450 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000451 except subprocess2.CalledProcessError:
452 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000453 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000454 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800455 files = self._Capture(
456 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000457 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000458 if not verbose:
459 # Make the output a little prettier. It's nice to have some whitespace
460 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000461 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000462 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000463
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000464 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000465 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000466 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
467 return self._Capture(['rev-parse', '--verify', 'HEAD'])
468
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000469 self._maybe_break_locks(options)
470
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000471 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000472 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000473
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000474 # See if the url has changed (the unittests use git://foo for the url, let
475 # that through).
476 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
477 return_early = False
478 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
479 # unit test pass. (and update the comment above)
480 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
481 # This allows devs to use experimental repos which have a different url
482 # but whose branch(s) are the same as official repos.
borenet@google.comb09097a2014-04-09 19:09:08 +0000483 if (current_url.rstrip('/') != url.rstrip('/') and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000484 url != 'git://foo' and
485 subprocess2.capture(
486 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
487 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000488 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000489 if not (options.force or options.reset):
490 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700491 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000492 # Switch over to the new upstream
493 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000494 if mirror:
495 with open(os.path.join(
496 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
497 'w') as fh:
498 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000499 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
500 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000501
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000502 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000503 else:
John Budorick882c91e2018-07-12 22:11:41 +0000504 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000505
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000506 if return_early:
507 return self._Capture(['rev-parse', '--verify', 'HEAD'])
508
msb@chromium.org5bde4852009-12-14 16:47:12 +0000509 cur_branch = self._GetCurrentBranch()
510
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000511 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000512 # 0) HEAD is detached. Probably from our initial clone.
513 # - make sure HEAD is contained by a named ref, then update.
514 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700515 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000516 # - try to rebase onto the new hash or branch
517 # 2) current branch is tracking a remote branch with local committed
518 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000519 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000520 # 3) current branch is tracking a remote branch w/or w/out changes, and
521 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000522 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000523 # 4) current branch is tracking a remote branch, but DEPS switches to a
524 # different remote branch, and
525 # a) current branch has no local changes, and --force:
526 # - checkout new branch
527 # b) current branch has local changes, and --force and --reset:
528 # - checkout new branch
529 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000530
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000531 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
532 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000533 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
534 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000535 if cur_branch is None:
536 upstream_branch = None
537 current_type = "detached"
538 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000539 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000540 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
541 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
542 current_type = "hash"
543 logging.debug("Current branch is not tracking an upstream (remote)"
544 " branch.")
545 elif upstream_branch.startswith('refs/remotes'):
546 current_type = "branch"
547 else:
548 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000549
Edward Lemur579c9862018-07-13 23:17:51 +0000550 self._SetFetchConfig(options)
551 self._Fetch(options, prune=options.force)
552
John Budorick882c91e2018-07-12 22:11:41 +0000553 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000554 # Update the remotes first so we have all the refs.
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000555 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000556 cwd=self.checkout_path)
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000557 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000558 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000559
John Budorick882c91e2018-07-12 22:11:41 +0000560 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200561
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000562 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000563 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000564 target = 'HEAD'
565 if options.upstream and upstream_branch:
566 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800567 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000568
msb@chromium.org786fb682010-06-02 15:16:23 +0000569 if current_type == 'detached':
570 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800571 # We just did a Scrub, this is as clean as it's going to get. In
572 # particular if HEAD is a commit that contains two versions of the same
573 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
574 # to actually "Clean" the checkout; that commit is uncheckoutable on this
575 # system. The best we can do is carry forward to the checkout step.
576 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000577 self._CheckClean(revision)
578 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000579 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000580 self.Print('Up-to-date; skipping checkout.')
581 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000582 # 'git checkout' may need to overwrite existing untracked files. Allow
583 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000584 self._Checkout(
585 options,
John Budorick882c91e2018-07-12 22:11:41 +0000586 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000587 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000588 quiet=True,
589 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000590 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000591 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000592 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000593 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700594 # Can't find a merge-base since we don't know our upstream. That makes
595 # this command VERY likely to produce a rebase failure. For now we
596 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000597 upstream_branch = self.remote
598 if options.revision or deps_revision:
599 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700600 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700601 printed_path=printed_path, merge=options.merge)
602 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000603 elif rev_type == 'hash':
604 # case 2
605 self._AttemptRebase(upstream_branch, file_list, options,
606 newbase=revision, printed_path=printed_path,
607 merge=options.merge)
608 printed_path = True
609 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000610 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000611 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000612 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000613 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000614 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000615 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000616 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000617 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
618 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000619 force_switch = False
620 if options.force:
621 try:
John Budorick882c91e2018-07-12 22:11:41 +0000622 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000623 # case 4a
624 force_switch = True
625 except gclient_utils.Error as e:
626 if options.reset:
627 # case 4b
628 force_switch = True
629 else:
630 switch_error = '%s\n%s' % (e.message, switch_error)
631 if force_switch:
632 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000633 (upstream_branch, new_base))
634 switch_branch = 'gclient_' + remote_ref[1]
635 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000636 self._Checkout(options, switch_branch, force=True, quiet=True)
637 else:
638 # case 4c
639 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000640 else:
641 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800642 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000643 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000644 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000645 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000646 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000647 if options.merge:
648 merge_args.append('--ff')
649 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000650 merge_args.append('--ff-only')
651 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000652 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000653 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700654 rebase_files = []
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000655 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
656 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000657 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700658 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000659 printed_path = True
660 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000661 if not options.auto_rebase:
662 try:
663 action = self._AskForData(
664 'Cannot %s, attempt to rebase? '
665 '(y)es / (q)uit / (s)kip : ' %
666 ('merge' if options.merge else 'fast-forward merge'),
667 options)
668 except ValueError:
669 raise gclient_utils.Error('Invalid Character')
670 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700671 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000672 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000673 printed_path = True
674 break
675 elif re.match(r'quit|q', action, re.I):
676 raise gclient_utils.Error("Can't fast-forward, please merge or "
677 "rebase manually.\n"
678 "cd %s && git " % self.checkout_path
679 + "rebase %s" % upstream_branch)
680 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000681 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000682 return
683 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000684 self.Print('Input not recognized')
smut@google.com27c9c8a2014-09-11 19:57:55 +0000685 elif re.match("error: Your local changes to '.*' would be "
686 "overwritten by merge. Aborting.\nPlease, commit your "
687 "changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000688 e.stderr):
689 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000690 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700691 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000692 printed_path = True
693 raise gclient_utils.Error(e.stderr)
694 else:
695 # Some other problem happened with the merge
696 logging.error("Error during fast-forward merge in %s!" % self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000697 self.Print(e.stderr)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000698 raise
699 else:
700 # Fast-forward merge was successful
701 if not re.match('Already up-to-date.', merge_output) or verbose:
702 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700703 self.Print('_____ %s at %s' % (self.relpath, revision),
704 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000705 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000706 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000707 if not verbose:
708 # Make the output a little prettier. It's nice to have some
709 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000710 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000711
agablec3937b92016-10-25 10:13:03 -0700712 if file_list is not None:
713 file_list.extend(
714 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000715
716 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000717 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700718 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000719 '\nConflict while rebasing this branch.\n'
720 'Fix the conflict and run gclient again.\n'
721 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700722 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000723
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000724 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000725 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
726 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000727
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000728 # If --reset and --delete_unversioned_trees are specified, remove any
729 # untracked directories.
730 if options.reset and options.delete_unversioned_trees:
731 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
732 # merge-base by default), so doesn't include untracked files. So we use
733 # 'git ls-files --directory --others --exclude-standard' here directly.
734 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800735 ['-c', 'core.quotePath=false', 'ls-files',
736 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000737 self.checkout_path)
738 for path in (p for p in paths.splitlines() if p.endswith('/')):
739 full_path = os.path.join(self.checkout_path, path)
740 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000741 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000742 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000743
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000744 return self._Capture(['rev-parse', '--verify', 'HEAD'])
745
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000746 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000747 """Reverts local modifications.
748
749 All reverted files will be appended to file_list.
750 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000751 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000752 # revert won't work if the directory doesn't exist. It needs to
753 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000754 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000755 # Don't reuse the args.
756 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000757
758 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000759 if options.upstream:
760 if self._GetCurrentBranch():
761 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
762 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000763 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000764 if not deps_revision:
765 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000766 if deps_revision.startswith('refs/heads/'):
767 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700768 try:
769 deps_revision = self.GetUsableRev(deps_revision, options)
770 except NoUsableRevError as e:
771 # If the DEPS entry's url and hash changed, try to update the origin.
772 # See also http://crbug.com/520067.
773 logging.warn(
774 'Couldn\'t find usable revision, will retrying to update instead: %s',
775 e.message)
776 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000777
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000778 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800779 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000780
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800781 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000782 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000783
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000784 if file_list is not None:
785 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
786
787 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000788 """Returns revision"""
789 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000790
msb@chromium.orge28e4982009-09-25 20:51:45 +0000791 def runhooks(self, options, args, file_list):
792 self.status(options, args, file_list)
793
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000794 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000795 """Display status information."""
796 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000797 self.Print('________ couldn\'t run status in %s:\n'
798 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000799 else:
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700800 try:
801 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
802 except subprocess2.CalledProcessError:
803 merge_base = []
Aaron Gablef4068aa2017-12-12 15:14:09 -0800804 self._Run(
805 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
806 options, stdout=self.out_fh, always=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000807 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800808 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000809 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000810
smutae7ea312016-07-18 11:59:41 -0700811 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700812 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700813 sha1 = None
814 if not os.path.isdir(self.checkout_path):
815 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800816 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700817
818 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
819 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700820 else:
agable41e3a6c2016-10-20 11:36:56 -0700821 # May exist in origin, but we don't have it yet, so fetch and look
822 # again.
823 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700824 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
825 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700826
827 if not sha1:
828 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800829 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700830
831 return sha1
832
primiano@chromium.org1c127382015-02-17 11:15:40 +0000833 def GetGitBackupDirPath(self):
834 """Returns the path where the .git folder for the current project can be
835 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
836 return os.path.join(self._root_dir,
837 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
838
John Budorick882c91e2018-07-12 22:11:41 +0000839 def _GetMirror(self, url, options):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000840 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000841 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000842 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000843 mirror_kwargs = {
844 'print_func': self.filter,
John Budorick882c91e2018-07-12 22:11:41 +0000845 'refs': []
hinoka@google.comb1b54572014-04-16 22:29:23 +0000846 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000847 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
848 mirror_kwargs['refs'].append('refs/branch-heads/*')
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000849 if hasattr(options, 'with_tags') and options.with_tags:
850 mirror_kwargs['refs'].append('refs/tags/*')
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000851 return git_cache.Mirror(url, **mirror_kwargs)
852
John Budorick882c91e2018-07-12 22:11:41 +0000853 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800854 """Update a git mirror by fetching the latest commits from the remote,
855 unless mirror already contains revision whose type is sha1 hash.
856 """
John Budorick882c91e2018-07-12 22:11:41 +0000857 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800858 if options.verbose:
859 self.Print('skipping mirror update, it has rev=%s already' % revision,
860 timestamp=False)
861 return
862
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000863 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000864 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000865 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000866 depth = 10
867 else:
868 depth = 10000
869 else:
870 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000871 mirror.populate(verbose=options.verbose,
872 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000873 depth=depth,
874 ignore_lock=getattr(options, 'ignore_locks', False),
875 lock_timeout=getattr(options, 'lock_timeout', 0))
876 mirror.unlock()
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000877
John Budorick882c91e2018-07-12 22:11:41 +0000878 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000879 """Clone a git repository from the given URL.
880
msb@chromium.org786fb682010-06-02 15:16:23 +0000881 Once we've cloned the repo, we checkout a working branch if the specified
882 revision is a branch head. If it is a tag or a specific commit, then we
883 leave HEAD detached as it makes future updates simpler -- in this case the
884 user should first create a new branch or switch to an existing branch before
885 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000886 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000887 # git clone doesn't seem to insert a newline properly before printing
888 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000889 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +0000890 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +0000891 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000892 if self.cache_dir:
893 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000894 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000895 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000896 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +0000897 # If the parent directory does not exist, Git clone on Windows will not
898 # create it, so we need to do it manually.
899 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000900 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000901
902 template_dir = None
903 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +0000904 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000905 # In the case of a subproject, the pinned sha is not necessarily the
906 # head of the remote branch (so we can't just use --depth=N). Instead,
907 # we tell git to fetch all the remote objects from SHA..HEAD by means of
908 # a template git dir which has a 'shallow' file pointing to the sha.
909 template_dir = tempfile.mkdtemp(
910 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
911 dir=parent_dir)
912 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
913 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
914 template_file.write(revision)
915 clone_cmd.append('--template=' + template_dir)
916 else:
917 # Otherwise, we're just interested in the HEAD. Just use --depth.
918 clone_cmd.append('--depth=1')
919
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000920 tmp_dir = tempfile.mkdtemp(
921 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
922 dir=parent_dir)
923 try:
924 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +0100925 if self.print_outbuf:
926 print_stdout = True
927 stdout = gclient_utils.WriteToStdout(self.out_fh)
928 else:
929 print_stdout = False
930 stdout = self.out_fh
931 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
932 print_stdout=print_stdout, stdout=stdout)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000933 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000934 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
935 os.path.join(self.checkout_path, '.git'))
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000936 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000937 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000938 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000939 finally:
940 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000941 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000942 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000943 if template_dir:
944 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +0000945 self._SetFetchConfig(options)
946 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000947 revision = self._AutoFetchRef(options, revision)
948 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
949 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000950 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +0000951 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000952 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +0000953 ('Checked out %s to a detached HEAD. Before making any commits\n'
954 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
955 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +0000956 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000957
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000958 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000959 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000960 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000961 raise gclient_utils.Error("Background task requires input. Rerun "
962 "gclient with --jobs=1 so that\n"
963 "interaction is possible.")
964 try:
965 return raw_input(prompt)
966 except KeyboardInterrupt:
967 # Hide the exception.
968 sys.exit(1)
969
970
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000971 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000972 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000973 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000974 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800975 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000976 revision = upstream
977 if newbase:
978 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000979 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000980 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000981 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000982 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000983 printed_path = True
984 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000985 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000986
987 if merge:
988 merge_output = self._Capture(['merge', revision])
989 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000990 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000991 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000992
993 # Build the rebase command here using the args
994 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
995 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000996 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000997 rebase_cmd.append('--verbose')
998 if newbase:
999 rebase_cmd.extend(['--onto', newbase])
1000 rebase_cmd.append(upstream)
1001 if branch:
1002 rebase_cmd.append(branch)
1003
1004 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001005 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001006 except subprocess2.CalledProcessError, e:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001007 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
1008 re.match(r'cannot rebase: your index contains uncommitted changes',
1009 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001010 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001011 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001012 'Cannot rebase because of unstaged changes.\n'
1013 '\'git reset --hard HEAD\' ?\n'
1014 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001015 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001016 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001017 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001018 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001019 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001020 break
1021 elif re.match(r'quit|q', rebase_action, re.I):
1022 raise gclient_utils.Error("Please merge or rebase manually\n"
1023 "cd %s && git " % self.checkout_path
1024 + "%s" % ' '.join(rebase_cmd))
1025 elif re.match(r'show|s', rebase_action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001026 self.Print('%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001027 continue
1028 else:
1029 gclient_utils.Error("Input not recognized")
1030 continue
1031 elif re.search(r'^CONFLICT', e.stdout, re.M):
1032 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1033 "Fix the conflict and run gclient again.\n"
1034 "See 'man git-rebase' for details.\n")
1035 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001036 self.Print(e.stdout.strip())
1037 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001038 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1039 "manually.\ncd %s && git " %
1040 self.checkout_path
1041 + "%s" % ' '.join(rebase_cmd))
1042
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001043 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001044 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001045 # Make the output a little prettier. It's nice to have some
1046 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001047 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001048
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001049 @staticmethod
1050 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001051 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1052 if not ok:
1053 raise gclient_utils.Error('git version %s < minimum required %s' %
1054 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001055
John Budorick882c91e2018-07-12 22:11:41 +00001056 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001057 # Special case handling if all 3 conditions are met:
1058 # * the mirros have recently changed, but deps destination remains same,
1059 # * the git histories of mirrors are conflicting.
1060 # * git cache is used
1061 # This manifests itself in current checkout having invalid HEAD commit on
1062 # most git operations. Since git cache is used, just deleted the .git
1063 # folder, and re-create it by cloning.
1064 try:
1065 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1066 except subprocess2.CalledProcessError as e:
1067 if ('fatal: bad object HEAD' in e.stderr
1068 and self.cache_dir and self.cache_dir in url):
1069 self.Print((
1070 'Likely due to DEPS change with git cache_dir, '
1071 'the current commit points to no longer existing object.\n'
1072 '%s' % e)
1073 )
1074 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001075 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001076 else:
1077 raise
1078
msb@chromium.org786fb682010-06-02 15:16:23 +00001079 def _IsRebasing(self):
1080 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1081 # have a plumbing command to determine whether a rebase is in progress, so
1082 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1083 g = os.path.join(self.checkout_path, '.git')
1084 return (
1085 os.path.isdir(os.path.join(g, "rebase-merge")) or
1086 os.path.isdir(os.path.join(g, "rebase-apply")))
1087
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001088 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001089 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1090 if os.path.exists(lockfile):
1091 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001092 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001093 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1094 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001095 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001096
msb@chromium.org786fb682010-06-02 15:16:23 +00001097 # Make sure the tree is clean; see git-rebase.sh for reference
1098 try:
1099 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001100 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001101 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001102 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001103 '\tYou have unstaged changes.\n'
1104 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001105 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001106 try:
1107 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001108 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001109 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001110 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001111 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001112 '\tYour index contains uncommitted changes\n'
1113 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001114 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001115
agable83faed02016-10-24 14:37:10 -07001116 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001117 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1118 # reference by a commit). If not, error out -- most likely a rebase is
1119 # in progress, try to detect so we can give a better error.
1120 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001121 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1122 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001123 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001124 # Commit is not contained by any rev. See if the user is rebasing:
1125 if self._IsRebasing():
1126 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001127 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001128 '\tAlready in a conflict, i.e. (no branch).\n'
1129 '\tFix the conflict and run gclient again.\n'
1130 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1131 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001132 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001133 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001134 name = ('saved-by-gclient-' +
1135 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001136 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001137 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001138 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001139
msb@chromium.org5bde4852009-12-14 16:47:12 +00001140 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001141 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001142 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001143 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001144 return None
1145 return branch
1146
borenet@google.comc3e09d22014-04-10 13:58:18 +00001147 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001148 kwargs.setdefault('cwd', self.checkout_path)
1149 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001150 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001151 env = scm.GIT.ApplyEnvVars(kwargs)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001152 ret = subprocess2.check_output(['git'] + args, env=env, **kwargs)
1153 if strip:
1154 ret = ret.strip()
1155 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001156
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001157 def _Checkout(self, options, ref, force=False, quiet=None):
1158 """Performs a 'git-checkout' operation.
1159
1160 Args:
1161 options: The configured option set
1162 ref: (str) The branch/commit to checkout
1163 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1164 'None', the behavior is inferred from 'options.verbose'.
1165 Returns: (str) The output of the checkout operation
1166 """
1167 if quiet is None:
1168 quiet = (not options.verbose)
1169 checkout_args = ['checkout']
1170 if force:
1171 checkout_args.append('--force')
1172 if quiet:
1173 checkout_args.append('--quiet')
1174 checkout_args.append(ref)
1175 return self._Capture(checkout_args)
1176
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001177 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1178 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001179 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemurd64781e2018-07-11 23:09:55 +00001180 # When a mirror is configured, it fetches only the refs/heads, and possibly
1181 # the refs/branch-heads and refs/tags, but not the refs/changes. So, if
1182 # we're asked to fetch a refs/changes ref from the mirror, it won't have it.
1183 # This makes sure that we always fetch refs/changes directly from the
1184 # repository and not from the mirror.
1185 if refspec and refspec.startswith('refs/changes'):
1186 remote, _ = gclient_utils.SplitUrlRevision(self.url)
1187 # Make sure that we fetch the (remote) refs/changes/xx ref to the (local)
1188 # refs/changes/xx ref.
1189 if ':' not in refspec:
1190 refspec += ':' + refspec
dnj@chromium.org680f2172014-06-25 00:39:32 +00001191 fetch_cmd = cfg + [
1192 'fetch',
1193 remote or self.remote,
1194 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001195 if refspec:
1196 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001197
1198 if prune:
1199 fetch_cmd.append('--prune')
1200 if options.verbose:
1201 fetch_cmd.append('--verbose')
1202 elif quiet:
1203 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001204 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001205
1206 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1207 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1208
Edward Lemur579c9862018-07-13 23:17:51 +00001209 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001210 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1211 if requested."""
Edward Lemur579c9862018-07-13 23:17:51 +00001212 if options.reset:
1213 try:
1214 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1215 options)
1216 self._Run(['config', 'remote.%s.fetch' % self.remote,
1217 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1218 except subprocess2.CalledProcessError as e:
1219 # If exit code was 5, it means we attempted to unset a config that
1220 # didn't exist. Ignore it.
1221 if e.returncode != 5:
1222 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001223 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001224 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001225 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1226 '^\\+refs/branch-heads/\\*:.*$']
1227 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001228 if hasattr(options, 'with_tags') and options.with_tags:
1229 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1230 '+refs/tags/*:refs/tags/*',
1231 '^\\+refs/tags/\\*:.*$']
1232 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001233
John Budorick882c91e2018-07-12 22:11:41 +00001234 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001235 """Attempts to fetch |revision| if not available in local repo.
1236
1237 Returns possibly updated revision."""
John Budorick882c91e2018-07-12 22:11:41 +00001238 try:
1239 self._Capture(['rev-parse', revision])
1240 except subprocess2.CalledProcessError:
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001241 self._Fetch(options, refspec=revision)
1242 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1243 return revision
1244
dnj@chromium.org680f2172014-06-25 00:39:32 +00001245 def _Run(self, args, options, show_header=True, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001246 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001247 kwargs.setdefault('cwd', self.checkout_path)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001248 kwargs.setdefault('stdout', self.out_fh)
szager@chromium.org848fd492014-04-09 19:06:44 +00001249 kwargs['filter_fn'] = self.filter
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001250 kwargs.setdefault('print_stdout', False)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001251 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001252 cmd = ['git'] + args
dnj@chromium.org680f2172014-06-25 00:39:32 +00001253 if show_header:
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001254 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1255 else:
1256 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001257
1258
1259class CipdPackage(object):
1260 """A representation of a single CIPD package."""
1261
John Budorickd3ba72b2018-03-20 12:27:42 -07001262 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001263 self._authority_for_subdir = authority_for_subdir
1264 self._name = name
1265 self._version = version
1266
1267 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001268 def authority_for_subdir(self):
1269 """Whether this package has authority to act on behalf of its subdir.
1270
1271 Some operations should only be performed once per subdirectory. A package
1272 that has authority for its subdirectory is the only package that should
1273 perform such operations.
1274
1275 Returns:
1276 bool; whether this package has subdir authority.
1277 """
1278 return self._authority_for_subdir
1279
1280 @property
1281 def name(self):
1282 return self._name
1283
1284 @property
1285 def version(self):
1286 return self._version
1287
1288
1289class CipdRoot(object):
1290 """A representation of a single CIPD root."""
1291 def __init__(self, root_dir, service_url):
1292 self._all_packages = set()
1293 self._mutator_lock = threading.Lock()
1294 self._packages_by_subdir = collections.defaultdict(list)
1295 self._root_dir = root_dir
1296 self._service_url = service_url
1297
1298 def add_package(self, subdir, package, version):
1299 """Adds a package to this CIPD root.
1300
1301 As far as clients are concerned, this grants both root and subdir authority
1302 to packages arbitrarily. (The implementation grants root authority to the
1303 first package added and subdir authority to the first package added for that
1304 subdir, but clients should not depend on or expect that behavior.)
1305
1306 Args:
1307 subdir: str; relative path to where the package should be installed from
1308 the cipd root directory.
1309 package: str; the cipd package name.
1310 version: str; the cipd package version.
1311 Returns:
1312 CipdPackage; the package that was created and added to this root.
1313 """
1314 with self._mutator_lock:
1315 cipd_package = CipdPackage(
1316 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001317 not self._packages_by_subdir[subdir])
1318 self._all_packages.add(cipd_package)
1319 self._packages_by_subdir[subdir].append(cipd_package)
1320 return cipd_package
1321
1322 def packages(self, subdir):
1323 """Get the list of configured packages for the given subdir."""
1324 return list(self._packages_by_subdir[subdir])
1325
1326 def clobber(self):
1327 """Remove the .cipd directory.
1328
1329 This is useful for forcing ensure to redownload and reinitialize all
1330 packages.
1331 """
1332 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001333 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001334 try:
1335 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1336 except OSError:
1337 if os.path.exists(cipd_cache_dir):
1338 raise
1339
1340 @contextlib.contextmanager
1341 def _create_ensure_file(self):
1342 try:
1343 ensure_file = None
1344 with tempfile.NamedTemporaryFile(
1345 suffix='.ensure', delete=False) as ensure_file:
1346 for subdir, packages in sorted(self._packages_by_subdir.iteritems()):
1347 ensure_file.write('@Subdir %s\n' % subdir)
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001348 for package in sorted(packages, key=lambda p: p.name):
John Budorick0f7b2002018-01-19 15:46:17 -08001349 ensure_file.write('%s %s\n' % (package.name, package.version))
1350 ensure_file.write('\n')
1351 yield ensure_file.name
1352 finally:
1353 if ensure_file is not None and os.path.exists(ensure_file.name):
1354 os.remove(ensure_file.name)
1355
1356 def ensure(self):
1357 """Run `cipd ensure`."""
1358 with self._mutator_lock:
1359 with self._create_ensure_file() as ensure_file:
1360 cmd = [
1361 'cipd', 'ensure',
1362 '-log-level', 'error',
1363 '-root', self.root_dir,
1364 '-ensure-file', ensure_file,
1365 ]
1366 gclient_utils.CheckCallAndFilterAndHeader(cmd)
1367
John Budorickd3ba72b2018-03-20 12:27:42 -07001368 def run(self, command):
1369 if command == 'update':
1370 self.ensure()
1371 elif command == 'revert':
1372 self.clobber()
1373 self.ensure()
1374
John Budorick0f7b2002018-01-19 15:46:17 -08001375 def created_package(self, package):
1376 """Checks whether this root created the given package.
1377
1378 Args:
1379 package: CipdPackage; the package to check.
1380 Returns:
1381 bool; whether this root created the given package.
1382 """
1383 return package in self._all_packages
1384
1385 @property
1386 def root_dir(self):
1387 return self._root_dir
1388
1389 @property
1390 def service_url(self):
1391 return self._service_url
1392
1393
1394class CipdWrapper(SCMWrapper):
1395 """Wrapper for CIPD.
1396
1397 Currently only supports chrome-infra-packages.appspot.com.
1398 """
John Budorick3929e9e2018-02-04 18:18:07 -08001399 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001400
1401 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1402 out_cb=None, root=None, package=None):
1403 super(CipdWrapper, self).__init__(
1404 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1405 out_cb=out_cb)
1406 assert root.created_package(package)
1407 self._package = package
1408 self._root = root
1409
1410 #override
1411 def GetCacheMirror(self):
1412 return None
1413
1414 #override
1415 def GetActualRemoteURL(self, options):
1416 return self._root.service_url
1417
1418 #override
1419 def DoesRemoteURLMatch(self, options):
1420 del options
1421 return True
1422
1423 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001424 """Does nothing.
1425
1426 CIPD packages should be reverted at the root by running
1427 `CipdRoot.run('revert')`.
1428 """
1429 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001430
1431 def diff(self, options, args, file_list):
1432 """CIPD has no notion of diffing."""
1433 pass
1434
1435 def pack(self, options, args, file_list):
1436 """CIPD has no notion of diffing."""
1437 pass
1438
1439 def revinfo(self, options, args, file_list):
1440 """Grab the instance ID."""
1441 try:
1442 tmpdir = tempfile.mkdtemp()
1443 describe_json_path = os.path.join(tmpdir, 'describe.json')
1444 cmd = [
1445 'cipd', 'describe',
1446 self._package.name,
1447 '-log-level', 'error',
1448 '-version', self._package.version,
1449 '-json-output', describe_json_path
1450 ]
1451 gclient_utils.CheckCallAndFilter(
1452 cmd, filter_fn=lambda _line: None, print_stdout=False)
1453 with open(describe_json_path) as f:
1454 describe_json = json.load(f)
1455 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1456 finally:
1457 gclient_utils.rmtree(tmpdir)
1458
1459 def status(self, options, args, file_list):
1460 pass
1461
1462 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001463 """Does nothing.
1464
1465 CIPD packages should be updated at the root by running
1466 `CipdRoot.run('update')`.
1467 """
1468 pass