blob: 4407ea96104c7650a63efd661b8702fa9d8214e5 [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
Raul Tambreb946b232019-03-26 14:48:46 +000021
22try:
23 import urlparse
24except ImportError: # For Py3 compatibility
25 import urllib.parse as urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000026
27import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +000028import git_cache
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000029import scm
borenet@google.comb2256212014-05-07 20:57:28 +000030import shutil
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000031import subprocess2
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000032
33
szager@chromium.org71cbb502013-04-19 23:30:15 +000034THIS_FILE_PATH = os.path.abspath(__file__)
35
hinoka@google.com2f2ca142014-01-07 03:59:18 +000036GSUTIL_DEFAULT_PATH = os.path.join(
hinoka@chromium.orgb091aa52014-12-20 01:47:31 +000037 os.path.dirname(os.path.abspath(__file__)), 'gsutil.py')
hinoka@google.com2f2ca142014-01-07 03:59:18 +000038
maruel@chromium.org79d62372015-06-01 18:50:55 +000039
smutae7ea312016-07-18 11:59:41 -070040class NoUsableRevError(gclient_utils.Error):
41 """Raised if requested revision isn't found in checkout."""
42
43
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000044class DiffFiltererWrapper(object):
45 """Simple base class which tracks which file is being diffed and
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000046 replaces instances of its file name in the original and
agable41e3a6c2016-10-20 11:36:56 -070047 working copy lines of the git diff output."""
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000048 index_string = None
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000049 original_prefix = "--- "
50 working_prefix = "+++ "
51
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000052 def __init__(self, relpath, print_func):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000053 # Note that we always use '/' as the path separator to be
agable41e3a6c2016-10-20 11:36:56 -070054 # consistent with cygwin-style output on Windows
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000055 self._relpath = relpath.replace("\\", "/")
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000056 self._current_file = None
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000057 self._print_func = print_func
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000058
maruel@chromium.org6e29d572010-06-04 17:32:20 +000059 def SetCurrentFile(self, current_file):
60 self._current_file = current_file
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000061
iannucci@chromium.org3830a672013-02-19 20:15:14 +000062 @property
63 def _replacement_file(self):
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000064 return posixpath.join(self._relpath, self._current_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000065
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000066 def _Replace(self, line):
67 return line.replace(self._current_file, self._replacement_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000068
69 def Filter(self, line):
70 if (line.startswith(self.index_string)):
71 self.SetCurrentFile(line[len(self.index_string):])
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000072 line = self._Replace(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000073 else:
74 if (line.startswith(self.original_prefix) or
75 line.startswith(self.working_prefix)):
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000076 line = self._Replace(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000077 self._print_func(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000078
79
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000080class GitDiffFilterer(DiffFiltererWrapper):
81 index_string = "diff --git "
82
83 def SetCurrentFile(self, current_file):
84 # Get filename by parsing "a/<filename> b/<filename>"
85 self._current_file = current_file[:(len(current_file)/2)][2:]
86
87 def _Replace(self, line):
88 return re.sub("[a|b]/" + self._current_file, self._replacement_file, line)
89
90
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000091# SCMWrapper base class
92
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000093class SCMWrapper(object):
94 """Add necessary glue between all the supported SCM.
95
msb@chromium.orgd6504212010-01-13 17:34:31 +000096 This is the abstraction layer to bind to different SCM.
97 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000098 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
Edward Lemur231f5ea2018-01-31 19:02:36 +010099 out_cb=None, print_outbuf=False):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000100 self.url = url
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +0000101 self._root_dir = root_dir
102 if self._root_dir:
103 self._root_dir = self._root_dir.replace('/', os.sep)
104 self.relpath = relpath
105 if self.relpath:
106 self.relpath = self.relpath.replace('/', os.sep)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000107 if self.relpath and self._root_dir:
108 self.checkout_path = os.path.join(self._root_dir, self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000109 if out_fh is None:
110 out_fh = sys.stdout
111 self.out_fh = out_fh
112 self.out_cb = out_cb
Edward Lemur231f5ea2018-01-31 19:02:36 +0100113 self.print_outbuf = print_outbuf
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000114
115 def Print(self, *args, **kwargs):
116 kwargs.setdefault('file', self.out_fh)
117 if kwargs.pop('timestamp', True):
118 self.out_fh.write('[%s] ' % gclient_utils.Elapsed())
119 print(*args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000120
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000121 def RunCommand(self, command, options, args, file_list=None):
agabledebf6c82016-12-21 12:50:12 -0800122 commands = ['update', 'updatesingle', 'revert',
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000123 'revinfo', 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000124
125 if not command in commands:
126 raise gclient_utils.Error('Unknown command %s' % command)
127
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000128 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000129 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000130 command, self.__class__.__name__))
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000131
132 return getattr(self, command)(options, args, file_list)
133
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000134 @staticmethod
135 def _get_first_remote_url(checkout_path):
136 log = scm.GIT.Capture(
137 ['config', '--local', '--get-regexp', r'remote.*.url'],
138 cwd=checkout_path)
139 # Get the second token of the first line of the log.
140 return log.splitlines()[0].split(' ', 1)[1]
141
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000142 def GetCacheMirror(self):
Robert Iannuccia19649b2018-06-29 16:31:45 +0000143 if getattr(self, 'cache_dir', None):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000144 url, _ = gclient_utils.SplitUrlRevision(self.url)
145 return git_cache.Mirror(url)
146 return None
147
smut@google.comd33eab32014-07-07 19:35:18 +0000148 def GetActualRemoteURL(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000149 """Attempt to determine the remote URL for this SCMWrapper."""
smut@google.comd33eab32014-07-07 19:35:18 +0000150 # Git
borenet@google.combda475e2014-03-24 19:04:45 +0000151 if os.path.exists(os.path.join(self.checkout_path, '.git')):
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000152 actual_remote_url = self._get_first_remote_url(self.checkout_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000153
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000154 mirror = self.GetCacheMirror()
155 # If the cache is used, obtain the actual remote URL from there.
156 if (mirror and mirror.exists() and
157 mirror.mirror_path.replace('\\', '/') ==
158 actual_remote_url.replace('\\', '/')):
159 actual_remote_url = self._get_first_remote_url(mirror.mirror_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000160 return actual_remote_url
borenet@google.com88d10082014-03-21 17:24:48 +0000161 return None
162
borenet@google.com4e9be262014-04-08 19:40:30 +0000163 def DoesRemoteURLMatch(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000164 """Determine whether the remote URL of this checkout is the expected URL."""
165 if not os.path.exists(self.checkout_path):
166 # A checkout which doesn't exist can't be broken.
167 return True
168
smut@google.comd33eab32014-07-07 19:35:18 +0000169 actual_remote_url = self.GetActualRemoteURL(options)
borenet@google.com88d10082014-03-21 17:24:48 +0000170 if actual_remote_url:
borenet@google.com8156c9f2014-04-01 16:41:36 +0000171 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
172 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
borenet@google.com88d10082014-03-21 17:24:48 +0000173 else:
174 # This may occur if the self.checkout_path exists but does not contain a
agable41e3a6c2016-10-20 11:36:56 -0700175 # valid git checkout.
borenet@google.com88d10082014-03-21 17:24:48 +0000176 return False
177
borenet@google.comb09097a2014-04-09 19:09:08 +0000178 def _DeleteOrMove(self, force):
179 """Delete the checkout directory or move it out of the way.
180
181 Args:
182 force: bool; if True, delete the directory. Otherwise, just move it.
183 """
borenet@google.comb2256212014-05-07 20:57:28 +0000184 if force and os.environ.get('CHROME_HEADLESS') == '1':
185 self.Print('_____ Conflicting directory found in %s. Removing.'
186 % self.checkout_path)
187 gclient_utils.AddWarning('Conflicting directory %s deleted.'
188 % self.checkout_path)
189 gclient_utils.rmtree(self.checkout_path)
190 else:
191 bad_scm_dir = os.path.join(self._root_dir, '_bad_scm',
192 os.path.dirname(self.relpath))
193
194 try:
195 os.makedirs(bad_scm_dir)
196 except OSError as e:
197 if e.errno != errno.EEXIST:
198 raise
199
200 dest_path = tempfile.mkdtemp(
201 prefix=os.path.basename(self.relpath),
202 dir=bad_scm_dir)
203 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
204 % (self.checkout_path, dest_path))
205 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
206 % (self.checkout_path, dest_path))
207 shutil.move(self.checkout_path, dest_path)
borenet@google.comb09097a2014-04-09 19:09:08 +0000208
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000209
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000210class GitWrapper(SCMWrapper):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000211 """Wrapper for Git"""
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000212 name = 'git'
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000213 remote = 'origin'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000214
Robert Iannuccia19649b2018-06-29 16:31:45 +0000215 @property
216 def cache_dir(self):
217 try:
218 return git_cache.Mirror.GetCachePath()
219 except RuntimeError:
220 return None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000221
John Budorick0f7b2002018-01-19 15:46:17 -0800222 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000223 """Removes 'git+' fake prefix from git URL."""
Henrique Ferreiroe72279d2019-04-17 12:01:50 +0000224 if url and (url.startswith('git+http://') or
225 url.startswith('git+https://')):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000226 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800227 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000228 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
229 if self.out_cb:
230 filter_kwargs['predicate'] = self.out_cb
231 self.filter = gclient_utils.GitFilter(**filter_kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +0000232 self._running_under_rosetta = None
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000233
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000234 def GetCheckoutRoot(self):
235 return scm.GIT.GetCheckoutRoot(self.checkout_path)
236
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000237 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000238 """Returns the given revision's date in ISO-8601 format (which contains the
239 time zone)."""
240 # TODO(floitsch): get the time-stamp of the given revision and not just the
241 # time-stamp of the currently checked out revision.
242 return self._Capture(['log', '-n', '1', '--format=%ai'])
243
Aaron Gablef4068aa2017-12-12 15:14:09 -0800244 def _GetDiffFilenames(self, base):
245 """Returns the names of files modified since base."""
246 return self._Capture(
Raul Tambrecd862e32019-05-10 21:19:00 +0000247 # Filter to remove base if it is None.
248 list(filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only',
249 base])
250 )).split()
Aaron Gablef4068aa2017-12-12 15:14:09 -0800251
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000252 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 15:45:56 -0800253 _, revision = gclient_utils.SplitUrlRevision(self.url)
254 if not revision:
255 revision = 'refs/remotes/%s/master' % self.remote
256 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000257
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000258 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000259 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000260 repository.
261
262 The patch file is generated from a diff of the merge base of HEAD and
263 its upstream branch.
264 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700265 try:
266 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
267 except subprocess2.CalledProcessError:
268 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000269 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700270 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000271 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000272 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000273
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800274 def _Scrub(self, target, options):
275 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000276 quiet = []
277 if not options.verbose:
278 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800279 self._Run(['reset', '--hard', target] + quiet, options)
280 if options.force and options.delete_unversioned_trees:
281 # where `target` is a commit that contains both upper and lower case
282 # versions of the same file on a case insensitive filesystem, we are
283 # actually in a broken state here. The index will have both 'a' and 'A',
284 # but only one of them will exist on the disk. To progress, we delete
285 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800286 output = self._Capture([
287 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800288 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800289 # --porcelain (v1) looks like:
290 # XY filename
291 try:
292 filename = line[3:]
293 self.Print('_____ Deleting residual after reset: %r.' % filename)
294 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800295 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800296 except OSError:
297 pass
298
John Budorick882c91e2018-07-12 22:11:41 +0000299 def _FetchAndReset(self, revision, file_list, options):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800300 """Equivalent to git fetch; git reset."""
Edward Lemur579c9862018-07-13 23:17:51 +0000301 self._SetFetchConfig(options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000302
dnj@chromium.org680f2172014-06-25 00:39:32 +0000303 self._Fetch(options, prune=True, quiet=options.verbose)
John Budorick882c91e2018-07-12 22:11:41 +0000304 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000305 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800306 files = self._Capture(
307 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambreb946b232019-03-26 14:48:46 +0000308 file_list.extend(
Edward Lemur26a8b9f2019-08-15 20:46:44 +0000309 [os.path.join(self.checkout_path, f) for f in files])
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000310
szager@chromium.org8a139702014-06-20 15:55:01 +0000311 def _DisableHooks(self):
312 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
313 if not os.path.isdir(hook_dir):
314 return
315 for f in os.listdir(hook_dir):
316 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000317 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
318 if os.path.exists(disabled_hook_path):
319 os.remove(disabled_hook_path)
320 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000321
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000322 def _maybe_break_locks(self, options):
323 """This removes all .lock files from this repo's .git directory, if the
324 user passed the --break_repo_locks command line flag.
325
326 In particular, this will cleanup index.lock files, as well as ref lock
327 files.
328 """
329 if options.break_repo_locks:
330 git_dir = os.path.join(self.checkout_path, '.git')
331 for path, _, filenames in os.walk(git_dir):
332 for filename in filenames:
333 if filename.endswith('.lock'):
334 to_break = os.path.join(path, filename)
335 self.Print('breaking lock: %s' % (to_break,))
336 try:
337 os.remove(to_break)
338 except OSError as ex:
339 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
340 raise
341
Edward Lemur3acbc742019-05-30 17:57:35 +0000342 def apply_patch_ref(self, patch_repo, patch_rev, target_rev, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000343 file_list):
344 """Apply a patch on top of the revision we're synced at.
345
Edward Lemur3acbc742019-05-30 17:57:35 +0000346 The patch ref is given by |patch_repo|@|patch_rev|.
347 |target_rev| is usually the branch that the |patch_rev| was uploaded against
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000348 (e.g. 'refs/heads/main'), but this is not required.
Edward Lemur3acbc742019-05-30 17:57:35 +0000349
350 We cherry-pick all commits reachable from |patch_rev| on top of the curret
351 HEAD, excluding those reachable from |target_rev|
352 (i.e. git cherry-pick target_rev..patch_rev).
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000353
354 Graphically, it looks like this:
355
Edward Lemur3acbc742019-05-30 17:57:35 +0000356 ... -> o -> [possibly already landed commits] -> target_rev
357 \
358 -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000359
Edward Lemur3acbc742019-05-30 17:57:35 +0000360 The final checkout state is then:
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000361
Edward Lemur3acbc742019-05-30 17:57:35 +0000362 ... -> HEAD -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000363
364 After application, if |options.reset_patch_ref| is specified, we soft reset
Edward Lemur3acbc742019-05-30 17:57:35 +0000365 the cherry-picked changes, keeping them in git index only.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000366
367 Args:
Edward Lemur3acbc742019-05-30 17:57:35 +0000368 patch_repo: The patch origin.
369 e.g. 'https://foo.googlesource.com/bar'
370 patch_rev: The revision to patch.
371 e.g. 'refs/changes/1234/34/1'.
372 target_rev: The revision to use when finding the merge base.
373 Typically, the branch that the patch was uploaded against.
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000374 e.g. 'refs/heads/main' or 'refs/heads/infra/config'.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000375 options: The options passed to gclient.
376 file_list: A list where modified files will be appended.
377 """
378
Edward Lemurca7d8812018-07-24 17:42:45 +0000379 # Abort any cherry-picks in progress.
380 try:
381 self._Capture(['cherry-pick', '--abort'])
382 except subprocess2.CalledProcessError:
383 pass
384
Edward Lesmesc621b212018-03-21 20:26:56 -0400385 base_rev = self._Capture(['rev-parse', 'HEAD'])
Edward Lemura0ffbe42019-05-01 16:52:18 +0000386
Edward Lemur3acbc742019-05-30 17:57:35 +0000387 if not target_rev:
Edward Lemur4c5c8ab2019-06-07 15:58:13 +0000388 raise gclient_utils.Error('A target revision for the patch must be given')
Edward Lesmesf627d9f2020-07-23 19:50:50 +0000389 elif target_rev.startswith(('refs/heads/', 'refs/branch-heads')):
390 # If |target_rev| is in refs/heads/** or refs/branch-heads/**, try first
391 # to find the corresponding remote ref for it, since |target_rev| might
392 # point to a local ref which is not up to date with the corresponding
393 # remote ref.
Edward Lemur3acbc742019-05-30 17:57:35 +0000394 remote_ref = ''.join(scm.GIT.RefToRemoteRef(target_rev, self.remote))
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000395 self.Print('Trying the corresponding remote ref for %r: %r\n' % (
Edward Lemur3acbc742019-05-30 17:57:35 +0000396 target_rev, remote_ref))
397 if scm.GIT.IsValidRevision(self.checkout_path, remote_ref):
398 target_rev = remote_ref
399 elif not scm.GIT.IsValidRevision(self.checkout_path, target_rev):
400 # Fetch |target_rev| if it's not already available.
401 url, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmes93d80d82021-04-20 02:55:11 +0000402 mirror = self._GetMirror(url, options, target_rev)
Edward Lemur3acbc742019-05-30 17:57:35 +0000403 if mirror:
404 rev_type = 'branch' if target_rev.startswith('refs/') else 'hash'
405 self._UpdateMirrorIfNotContains(mirror, options, rev_type, target_rev)
406 self._Fetch(options, refspec=target_rev)
Edward Lemura0ffbe42019-05-01 16:52:18 +0000407
Edward Lemur3acbc742019-05-30 17:57:35 +0000408 self.Print('===Applying patch===')
409 self.Print('Revision to patch is %r @ %r.' % (patch_repo, patch_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000410 self.Print('Current dir is %r' % self.checkout_path)
Edward Lesmesc621b212018-03-21 20:26:56 -0400411 self._Capture(['reset', '--hard'])
danakjd5c0b562019-11-08 17:27:47 +0000412 self._Capture(['fetch', '--no-tags', patch_repo, patch_rev])
Edward Lemurca7d8812018-07-24 17:42:45 +0000413 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400414
Edward Lemur3acbc742019-05-30 17:57:35 +0000415 if not options.rebase_patch_ref:
416 self._Capture(['checkout', patch_rev])
Robert Iannuccic39a7782019-11-01 18:30:33 +0000417 # Adjust base_rev to be the first parent of our checked out patch ref;
418 # This will allow us to correctly extend `file_list`, and will show the
419 # correct file-list to programs which do `git diff --cached` expecting to
420 # see the patch diff.
421 base_rev = self._Capture(['rev-parse', patch_rev+'~'])
422
Edward Lemur3acbc742019-05-30 17:57:35 +0000423 else:
Robert Iannuccic39a7782019-11-01 18:30:33 +0000424 self.Print('Will cherrypick %r .. %r on top of %r.' % (
425 target_rev, patch_rev, base_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000426 try:
427 if scm.GIT.IsAncestor(self.checkout_path, patch_rev, target_rev):
428 # If |patch_rev| is an ancestor of |target_rev|, check it out.
Edward Lemurca7d8812018-07-24 17:42:45 +0000429 self._Capture(['checkout', patch_rev])
430 else:
431 # If a change was uploaded on top of another change, which has already
432 # landed, one of the commits in the cherry-pick range will be
433 # redundant, since it has already landed and its changes incorporated
434 # in the tree.
435 # We pass '--keep-redundant-commits' to ignore those changes.
Edward Lemur3acbc742019-05-30 17:57:35 +0000436 self._Capture(['cherry-pick', target_rev + '..' + patch_rev,
Edward Lemurca7d8812018-07-24 17:42:45 +0000437 '--keep-redundant-commits'])
438
Edward Lemur3acbc742019-05-30 17:57:35 +0000439 except subprocess2.CalledProcessError as e:
440 self.Print('Failed to apply patch.')
441 self.Print('Revision to patch was %r @ %r.' % (patch_repo, patch_rev))
442 self.Print('Tried to cherrypick %r .. %r on top of %r.' % (
443 target_rev, patch_rev, base_rev))
444 self.Print('Current dir is %r' % self.checkout_path)
445 self.Print('git returned non-zero exit status %s:\n%s' % (
Edward Lemur979fa782019-08-13 22:44:05 +0000446 e.returncode, e.stderr.decode('utf-8')))
Edward Lemur3acbc742019-05-30 17:57:35 +0000447 # Print the current status so that developers know what changes caused
448 # the patch failure, since git cherry-pick doesn't show that
449 # information.
450 self.Print(self._Capture(['status']))
451 try:
452 self._Capture(['cherry-pick', '--abort'])
453 except subprocess2.CalledProcessError:
454 pass
455 raise
Edward Lemurca7d8812018-07-24 17:42:45 +0000456
Edward Lemur3acbc742019-05-30 17:57:35 +0000457 if file_list is not None:
458 file_list.extend(self._GetDiffFilenames(base_rev))
Edward Lemurca7d8812018-07-24 17:42:45 +0000459
Edward Lesmesc621b212018-03-21 20:26:56 -0400460 if options.reset_patch_ref:
461 self._Capture(['reset', '--soft', base_rev])
462
msb@chromium.orge28e4982009-09-25 20:51:45 +0000463 def update(self, options, args, file_list):
464 """Runs git to update or transparently checkout the working copy.
465
466 All updated files will be appended to file_list.
467
468 Raises:
469 Error: if can't get URL for relative path.
470 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000471 if args:
472 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
473
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000474 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000475
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000476 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000477 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000478 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000479 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000480 # Override the revision number.
481 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000482 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000483 # Check again for a revision in case an initial ref was specified
484 # in the url, for example bla.git@refs/heads/custombranch
485 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000486 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000487 if not revision:
Josip Sokcevic091f5ac2021-01-14 23:14:21 +0000488 # If a dependency is not pinned, track the default remote branch.
489 revision = scm.GIT.GetRemoteHeadRef(self.checkout_path, self.url,
490 self.remote)
Edward Lesmes4ea67bb2021-04-20 17:33:52 +0000491 if revision.startswith('origin/'):
492 revision = 'refs/remotes/' + revision
msb@chromium.orge28e4982009-09-25 20:51:45 +0000493
szager@chromium.org8a139702014-06-20 15:55:01 +0000494 if managed:
495 self._DisableHooks()
496
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000497 printed_path = False
498 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000499 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700500 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000501 verbose = ['--verbose']
502 printed_path = True
503
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000504 revision_ref = revision
505 if ':' in revision:
506 revision_ref, _, revision = revision.partition(':')
507
Edward Lesmes8073a502020-04-15 02:11:14 +0000508 if revision_ref.startswith('refs/branch-heads'):
509 options.with_branch_heads = True
510
Edward Lesmes93d80d82021-04-20 02:55:11 +0000511 mirror = self._GetMirror(url, options, revision_ref)
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000512 if mirror:
513 url = mirror.mirror_path
Edward Lemurdb5c5ad2019-03-07 16:00:24 +0000514
John Budorick882c91e2018-07-12 22:11:41 +0000515 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
516 if remote_ref:
517 # Rewrite remote refs to their local equivalents.
518 revision = ''.join(remote_ref)
519 rev_type = "branch"
520 elif revision.startswith('refs/'):
521 # Local branch? We probably don't want to support, since DEPS should
522 # always specify branches as they are in the upstream repo.
523 rev_type = "branch"
524 else:
525 # hash is also a tag, only make a distinction at checkout
526 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000527
primiano@chromium.org1c127382015-02-17 11:15:40 +0000528 # If we are going to introduce a new project, there is a possibility that
529 # we are syncing back to a state where the project was originally a
530 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
531 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
532 # In such case, we might have a backup of the former .git folder, which can
533 # be used to avoid re-fetching the entire repo again (useful for bisects).
534 backup_dir = self.GetGitBackupDirPath()
535 target_dir = os.path.join(self.checkout_path, '.git')
536 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
537 gclient_utils.safe_makedirs(self.checkout_path)
538 os.rename(backup_dir, target_dir)
539 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800540 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000541
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000542 if (not os.path.exists(self.checkout_path) or
543 (os.path.isdir(self.checkout_path) and
544 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000545 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000546 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000547 try:
John Budorick882c91e2018-07-12 22:11:41 +0000548 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000549 except subprocess2.CalledProcessError:
550 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000551 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000552 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800553 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20 +0000554 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambrec2f74c12019-03-19 05:55:53 +0000555 file_list.extend(
Edward Lemur979fa782019-08-13 22:44:05 +0000556 [os.path.join(self.checkout_path, f) for f in files])
John Budorick21a51b32018-09-19 19:39:20 +0000557 if mirror:
558 self._Capture(
559 ['remote', 'set-url', '--push', 'origin', mirror.url])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000560 if not verbose:
561 # Make the output a little prettier. It's nice to have some whitespace
562 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000563 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000564 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000565
John Budorick21a51b32018-09-19 19:39:20 +0000566 if mirror:
567 self._Capture(
568 ['remote', 'set-url', '--push', 'origin', mirror.url])
569
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000570 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000571 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000572 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
573 return self._Capture(['rev-parse', '--verify', 'HEAD'])
574
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000575 self._maybe_break_locks(options)
576
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000577 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000578 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000579
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000580 # See if the url has changed (the unittests use git://foo for the url, let
581 # that through).
582 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
583 return_early = False
584 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
585 # unit test pass. (and update the comment above)
586 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
587 # This allows devs to use experimental repos which have a different url
588 # but whose branch(s) are the same as official repos.
Raul Tambrecd862e32019-05-10 21:19:00 +0000589 if (current_url.rstrip('/') != url.rstrip('/') and url != 'git://foo' and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000590 subprocess2.capture(
Aaron Gableac9b0f32019-04-18 17:38:37 +0000591 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000592 cwd=self.checkout_path).strip() != 'False'):
Anthony Polito486f1812020-08-04 23:40:33 +0000593 self.Print('_____ switching %s from %s to new upstream %s' % (
594 self.relpath, current_url, url))
iannucci@chromium.org78514212014-08-20 23:08:00 +0000595 if not (options.force or options.reset):
596 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700597 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000598 # Switch over to the new upstream
599 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000600 if mirror:
601 with open(os.path.join(
602 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
603 'w') as fh:
604 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000605 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
606 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000607
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000608 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000609 else:
John Budorick882c91e2018-07-12 22:11:41 +0000610 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000611
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000612 if return_early:
613 return self._Capture(['rev-parse', '--verify', 'HEAD'])
614
msb@chromium.org5bde4852009-12-14 16:47:12 +0000615 cur_branch = self._GetCurrentBranch()
616
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000617 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000618 # 0) HEAD is detached. Probably from our initial clone.
619 # - make sure HEAD is contained by a named ref, then update.
620 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700621 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000622 # - try to rebase onto the new hash or branch
623 # 2) current branch is tracking a remote branch with local committed
624 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000625 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000626 # 3) current branch is tracking a remote branch w/or w/out changes, and
627 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000628 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000629 # 4) current branch is tracking a remote branch, but DEPS switches to a
630 # different remote branch, and
631 # a) current branch has no local changes, and --force:
632 # - checkout new branch
633 # b) current branch has local changes, and --force and --reset:
634 # - checkout new branch
635 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000636
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000637 # GetUpstreamBranch returns something like 'refs/remotes/origin/main' for
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000638 # a tracking branch
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000639 # or 'main' if not a tracking branch (it's based on a specific rev/hash)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000640 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000641 if cur_branch is None:
642 upstream_branch = None
643 current_type = "detached"
644 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000645 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000646 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
647 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
648 current_type = "hash"
649 logging.debug("Current branch is not tracking an upstream (remote)"
650 " branch.")
651 elif upstream_branch.startswith('refs/remotes'):
652 current_type = "branch"
653 else:
654 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000655
Edward Lemur579c9862018-07-13 23:17:51 +0000656 self._SetFetchConfig(options)
Edward Lemur579c9862018-07-13 23:17:51 +0000657
Michael Spang73fac912019-03-08 18:44:19 +0000658 # Fetch upstream if we don't already have |revision|.
John Budorick882c91e2018-07-12 22:11:41 +0000659 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
Michael Spang73fac912019-03-08 18:44:19 +0000660 self._Fetch(options, prune=options.force)
661
662 if not scm.GIT.IsValidRevision(self.checkout_path, revision,
663 sha_only=True):
664 # Update the remotes first so we have all the refs.
665 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
666 cwd=self.checkout_path)
667 if verbose:
668 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000669
John Budorick882c91e2018-07-12 22:11:41 +0000670 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200671
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000672 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000673 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000674 target = 'HEAD'
675 if options.upstream and upstream_branch:
676 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800677 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000678
msb@chromium.org786fb682010-06-02 15:16:23 +0000679 if current_type == 'detached':
680 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800681 # We just did a Scrub, this is as clean as it's going to get. In
682 # particular if HEAD is a commit that contains two versions of the same
683 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
684 # to actually "Clean" the checkout; that commit is uncheckoutable on this
685 # system. The best we can do is carry forward to the checkout step.
686 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000687 self._CheckClean(revision)
688 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000689 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000690 self.Print('Up-to-date; skipping checkout.')
691 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000692 # 'git checkout' may need to overwrite existing untracked files. Allow
693 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000694 self._Checkout(
695 options,
John Budorick882c91e2018-07-12 22:11:41 +0000696 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000697 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000698 quiet=True,
699 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000700 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000701 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000702 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000703 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700704 # Can't find a merge-base since we don't know our upstream. That makes
705 # this command VERY likely to produce a rebase failure. For now we
706 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000707 upstream_branch = self.remote
708 if options.revision or deps_revision:
709 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700710 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700711 printed_path=printed_path, merge=options.merge)
712 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000713 elif rev_type == 'hash':
714 # case 2
715 self._AttemptRebase(upstream_branch, file_list, options,
716 newbase=revision, printed_path=printed_path,
717 merge=options.merge)
718 printed_path = True
719 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000720 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000721 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000722 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000723 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000724 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000725 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000726 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000727 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
728 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000729 force_switch = False
730 if options.force:
731 try:
John Budorick882c91e2018-07-12 22:11:41 +0000732 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000733 # case 4a
734 force_switch = True
735 except gclient_utils.Error as e:
736 if options.reset:
737 # case 4b
738 force_switch = True
739 else:
740 switch_error = '%s\n%s' % (e.message, switch_error)
741 if force_switch:
742 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000743 (upstream_branch, new_base))
744 switch_branch = 'gclient_' + remote_ref[1]
745 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000746 self._Checkout(options, switch_branch, force=True, quiet=True)
747 else:
748 # case 4c
749 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000750 else:
751 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800752 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000753 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000754 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000755 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000756 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000757 if options.merge:
758 merge_args.append('--ff')
759 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000760 merge_args.append('--ff-only')
761 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000762 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000763 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700764 rebase_files = []
Edward Lemur979fa782019-08-13 22:44:05 +0000765 if re.match(b'fatal: Not possible to fast-forward, aborting.',
766 e.stderr):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000767 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000768 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700769 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000770 printed_path = True
771 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000772 if not options.auto_rebase:
773 try:
774 action = self._AskForData(
775 'Cannot %s, attempt to rebase? '
776 '(y)es / (q)uit / (s)kip : ' %
777 ('merge' if options.merge else 'fast-forward merge'),
778 options)
779 except ValueError:
780 raise gclient_utils.Error('Invalid Character')
781 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700782 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000783 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000784 printed_path = True
785 break
786 elif re.match(r'quit|q', action, re.I):
787 raise gclient_utils.Error("Can't fast-forward, please merge or "
788 "rebase manually.\n"
789 "cd %s && git " % self.checkout_path
790 + "rebase %s" % upstream_branch)
791 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000792 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000793 return
794 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000795 self.Print('Input not recognized')
Edward Lemur979fa782019-08-13 22:44:05 +0000796 elif re.match(b"error: Your local changes to '.*' would be "
797 b"overwritten by merge. Aborting.\nPlease, commit your "
798 b"changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000799 e.stderr):
800 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000801 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700802 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000803 printed_path = True
Edward Lemur979fa782019-08-13 22:44:05 +0000804 raise gclient_utils.Error(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000805 else:
806 # Some other problem happened with the merge
807 logging.error("Error during fast-forward merge in %s!" % self.relpath)
Edward Lemur979fa782019-08-13 22:44:05 +0000808 self.Print(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000809 raise
810 else:
811 # Fast-forward merge was successful
812 if not re.match('Already up-to-date.', merge_output) or verbose:
813 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700814 self.Print('_____ %s at %s' % (self.relpath, revision),
815 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000816 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000817 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000818 if not verbose:
819 # Make the output a little prettier. It's nice to have some
820 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000821 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000822
agablec3937b92016-10-25 10:13:03 -0700823 if file_list is not None:
824 file_list.extend(
825 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000826
827 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000828 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700829 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000830 '\nConflict while rebasing this branch.\n'
831 'Fix the conflict and run gclient again.\n'
832 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700833 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000834
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000835 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000836 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
837 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000838
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000839 # If --reset and --delete_unversioned_trees are specified, remove any
840 # untracked directories.
841 if options.reset and options.delete_unversioned_trees:
842 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
843 # merge-base by default), so doesn't include untracked files. So we use
844 # 'git ls-files --directory --others --exclude-standard' here directly.
845 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800846 ['-c', 'core.quotePath=false', 'ls-files',
847 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000848 self.checkout_path)
849 for path in (p for p in paths.splitlines() if p.endswith('/')):
850 full_path = os.path.join(self.checkout_path, path)
851 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000852 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000853 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000854
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000855 return self._Capture(['rev-parse', '--verify', 'HEAD'])
856
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000857 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000858 """Reverts local modifications.
859
860 All reverted files will be appended to file_list.
861 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000862 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000863 # revert won't work if the directory doesn't exist. It needs to
864 # checkout instead.
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000865 self.Print('_____ %s is missing, syncing instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000866 # Don't reuse the args.
867 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000868
869 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000870 if options.upstream:
871 if self._GetCurrentBranch():
872 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
873 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000874 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000875 if not deps_revision:
876 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000877 if deps_revision.startswith('refs/heads/'):
878 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700879 try:
880 deps_revision = self.GetUsableRev(deps_revision, options)
881 except NoUsableRevError as e:
882 # If the DEPS entry's url and hash changed, try to update the origin.
883 # See also http://crbug.com/520067.
John Budorickd94f8ea2020-03-27 15:55:24 +0000884 logging.warning(
885 "Couldn't find usable revision, will retrying to update instead: %s",
smutae7ea312016-07-18 11:59:41 -0700886 e.message)
887 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000888
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000889 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800890 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000891
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800892 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000893 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000894
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000895 if file_list is not None:
896 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
897
898 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000899 """Returns revision"""
900 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000901
msb@chromium.orge28e4982009-09-25 20:51:45 +0000902 def runhooks(self, options, args, file_list):
903 self.status(options, args, file_list)
904
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000905 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000906 """Display status information."""
907 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000908 self.Print('________ couldn\'t run status in %s:\n'
909 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000910 else:
Anthony Politobb457342019-11-15 22:26:01 +0000911 merge_base = []
912 if self.url:
913 _, base_rev = gclient_utils.SplitUrlRevision(self.url)
914 if base_rev:
915 merge_base = [base_rev]
Aaron Gablef4068aa2017-12-12 15:14:09 -0800916 self._Run(
917 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
Edward Lemur24146be2019-08-01 21:44:52 +0000918 options, always_show_header=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000919 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800920 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000921 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000922
smutae7ea312016-07-18 11:59:41 -0700923 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700924 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700925 sha1 = None
926 if not os.path.isdir(self.checkout_path):
927 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800928 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700929
930 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
931 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700932 else:
agable41e3a6c2016-10-20 11:36:56 -0700933 # May exist in origin, but we don't have it yet, so fetch and look
934 # again.
935 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700936 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
937 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700938
939 if not sha1:
940 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800941 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700942
943 return sha1
944
primiano@chromium.org1c127382015-02-17 11:15:40 +0000945 def GetGitBackupDirPath(self):
946 """Returns the path where the .git folder for the current project can be
947 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
948 return os.path.join(self._root_dir,
949 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
950
Edward Lesmes93d80d82021-04-20 02:55:11 +0000951 def _GetMirror(self, url, options, revision_ref=None):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000952 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000953 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000954 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000955 mirror_kwargs = {
956 'print_func': self.filter,
Edward Lesmes93d80d82021-04-20 02:55:11 +0000957 'refs': []
hinoka@google.comb1b54572014-04-16 22:29:23 +0000958 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000959 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
960 mirror_kwargs['refs'].append('refs/branch-heads/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000961 elif revision_ref and revision_ref.startswith('refs/branch-heads/'):
962 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000963 if hasattr(options, 'with_tags') and options.with_tags:
964 mirror_kwargs['refs'].append('refs/tags/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000965 elif revision_ref and revision_ref.startswith('refs/tags/'):
966 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000967 return git_cache.Mirror(url, **mirror_kwargs)
968
John Budorick882c91e2018-07-12 22:11:41 +0000969 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800970 """Update a git mirror by fetching the latest commits from the remote,
971 unless mirror already contains revision whose type is sha1 hash.
972 """
John Budorick882c91e2018-07-12 22:11:41 +0000973 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800974 if options.verbose:
975 self.Print('skipping mirror update, it has rev=%s already' % revision,
976 timestamp=False)
977 return
978
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000979 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000980 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000981 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000982 depth = 10
983 else:
984 depth = 10000
985 else:
986 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000987 mirror.populate(verbose=options.verbose,
988 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000989 depth=depth,
Vadim Shtayura08049e22017-10-11 00:14:52 +0000990 lock_timeout=getattr(options, 'lock_timeout', 0))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000991
John Budorick882c91e2018-07-12 22:11:41 +0000992 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000993 """Clone a git repository from the given URL.
994
msb@chromium.org786fb682010-06-02 15:16:23 +0000995 Once we've cloned the repo, we checkout a working branch if the specified
996 revision is a branch head. If it is a tag or a specific commit, then we
997 leave HEAD detached as it makes future updates simpler -- in this case the
998 user should first create a new branch or switch to an existing branch before
999 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001000 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001001 # git clone doesn't seem to insert a newline properly before printing
1002 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001003 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +00001004 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +00001005 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001006 if self.cache_dir:
1007 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001008 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001009 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001010 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +00001011 # If the parent directory does not exist, Git clone on Windows will not
1012 # create it, so we need to do it manually.
1013 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001014 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001015
1016 template_dir = None
1017 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +00001018 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001019 # In the case of a subproject, the pinned sha is not necessarily the
1020 # head of the remote branch (so we can't just use --depth=N). Instead,
1021 # we tell git to fetch all the remote objects from SHA..HEAD by means of
1022 # a template git dir which has a 'shallow' file pointing to the sha.
1023 template_dir = tempfile.mkdtemp(
1024 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
1025 dir=parent_dir)
1026 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
1027 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
1028 template_file.write(revision)
1029 clone_cmd.append('--template=' + template_dir)
1030 else:
1031 # Otherwise, we're just interested in the HEAD. Just use --depth.
1032 clone_cmd.append('--depth=1')
1033
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001034 tmp_dir = tempfile.mkdtemp(
1035 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
1036 dir=parent_dir)
1037 try:
1038 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001039 if self.print_outbuf:
1040 print_stdout = True
Edward Lemur24146be2019-08-01 21:44:52 +00001041 filter_fn = None
Edward Lemur231f5ea2018-01-31 19:02:36 +01001042 else:
1043 print_stdout = False
Edward Lemur24146be2019-08-01 21:44:52 +00001044 filter_fn = self.filter
Edward Lemur231f5ea2018-01-31 19:02:36 +01001045 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
Edward Lemur24146be2019-08-01 21:44:52 +00001046 print_stdout=print_stdout, filter_fn=filter_fn)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001047 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +00001048 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
1049 os.path.join(self.checkout_path, '.git'))
Edward Lesmesd4e20f22020-07-15 21:11:08 +00001050 # TODO(https://github.com/git-for-windows/git/issues/2569): Remove once
1051 # fixed.
1052 if sys.platform.startswith('win'):
1053 try:
1054 self._Run(['config', '--unset', 'core.worktree'], options,
1055 cwd=self.checkout_path)
1056 except subprocess2.CalledProcessError:
1057 pass
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001058 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001059 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001060 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001061 finally:
1062 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001063 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001064 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001065 if template_dir:
1066 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +00001067 self._SetFetchConfig(options)
1068 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001069 revision = self._AutoFetchRef(options, revision)
1070 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1071 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +00001072 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +00001073 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001074 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +00001075 ('Checked out %s to a detached HEAD. Before making any commits\n'
1076 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1077 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +00001078 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001079
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001080 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001081 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001082 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001083 raise gclient_utils.Error("Background task requires input. Rerun "
1084 "gclient with --jobs=1 so that\n"
1085 "interaction is possible.")
Edward Lesmesae3586b2020-03-23 21:21:14 +00001086 return gclient_utils.AskForData(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001087
1088
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001089 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001090 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001091 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001092 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001093 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001094 revision = upstream
1095 if newbase:
1096 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001097 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001098 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001099 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001100 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001101 printed_path = True
1102 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001103 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001104
1105 if merge:
1106 merge_output = self._Capture(['merge', revision])
1107 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001108 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001109 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001110
1111 # Build the rebase command here using the args
1112 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1113 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001114 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001115 rebase_cmd.append('--verbose')
1116 if newbase:
1117 rebase_cmd.extend(['--onto', newbase])
1118 rebase_cmd.append(upstream)
1119 if branch:
1120 rebase_cmd.append(branch)
1121
1122 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001123 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
Raul Tambreb946b232019-03-26 14:48:46 +00001124 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001125 if (re.match(br'cannot rebase: you have unstaged changes', e.stderr) or
1126 re.match(br'cannot rebase: your index contains uncommitted changes',
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001127 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001128 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001129 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001130 'Cannot rebase because of unstaged changes.\n'
1131 '\'git reset --hard HEAD\' ?\n'
1132 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001133 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001134 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001135 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001136 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001137 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001138 break
1139 elif re.match(r'quit|q', rebase_action, re.I):
1140 raise gclient_utils.Error("Please merge or rebase manually\n"
1141 "cd %s && git " % self.checkout_path
1142 + "%s" % ' '.join(rebase_cmd))
1143 elif re.match(r'show|s', rebase_action, re.I):
Edward Lemur979fa782019-08-13 22:44:05 +00001144 self.Print('%s' % e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001145 continue
1146 else:
1147 gclient_utils.Error("Input not recognized")
1148 continue
Edward Lemur979fa782019-08-13 22:44:05 +00001149 elif re.search(br'^CONFLICT', e.stdout, re.M):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001150 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1151 "Fix the conflict and run gclient again.\n"
1152 "See 'man git-rebase' for details.\n")
1153 else:
Edward Lemur979fa782019-08-13 22:44:05 +00001154 self.Print(e.stdout.decode('utf-8').strip())
1155 self.Print('Rebase produced error output:\n%s' %
1156 e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001157 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1158 "manually.\ncd %s && git " %
1159 self.checkout_path
1160 + "%s" % ' '.join(rebase_cmd))
1161
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001162 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001163 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001164 # Make the output a little prettier. It's nice to have some
1165 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001166 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001167
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001168 @staticmethod
1169 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001170 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1171 if not ok:
1172 raise gclient_utils.Error('git version %s < minimum required %s' %
1173 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001174
John Budorick882c91e2018-07-12 22:11:41 +00001175 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001176 # Special case handling if all 3 conditions are met:
1177 # * the mirros have recently changed, but deps destination remains same,
1178 # * the git histories of mirrors are conflicting.
1179 # * git cache is used
1180 # This manifests itself in current checkout having invalid HEAD commit on
1181 # most git operations. Since git cache is used, just deleted the .git
1182 # folder, and re-create it by cloning.
1183 try:
1184 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1185 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001186 if (b'fatal: bad object HEAD' in e.stderr
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001187 and self.cache_dir and self.cache_dir in url):
1188 self.Print((
1189 'Likely due to DEPS change with git cache_dir, '
1190 'the current commit points to no longer existing object.\n'
1191 '%s' % e)
1192 )
1193 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001194 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001195 else:
1196 raise
1197
msb@chromium.org786fb682010-06-02 15:16:23 +00001198 def _IsRebasing(self):
1199 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1200 # have a plumbing command to determine whether a rebase is in progress, so
1201 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1202 g = os.path.join(self.checkout_path, '.git')
1203 return (
1204 os.path.isdir(os.path.join(g, "rebase-merge")) or
1205 os.path.isdir(os.path.join(g, "rebase-apply")))
1206
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001207 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001208 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1209 if os.path.exists(lockfile):
1210 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001211 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001212 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1213 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001214 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001215
msb@chromium.org786fb682010-06-02 15:16:23 +00001216 # Make sure the tree is clean; see git-rebase.sh for reference
1217 try:
1218 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001219 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001220 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001221 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001222 '\tYou have unstaged changes.\n'
1223 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001224 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001225 try:
1226 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001227 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001228 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001229 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001230 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001231 '\tYour index contains uncommitted changes\n'
1232 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001233 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001234
agable83faed02016-10-24 14:37:10 -07001235 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001236 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1237 # reference by a commit). If not, error out -- most likely a rebase is
1238 # in progress, try to detect so we can give a better error.
1239 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001240 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1241 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001242 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001243 # Commit is not contained by any rev. See if the user is rebasing:
1244 if self._IsRebasing():
1245 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001246 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001247 '\tAlready in a conflict, i.e. (no branch).\n'
1248 '\tFix the conflict and run gclient again.\n'
1249 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1250 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001251 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001252 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001253 name = ('saved-by-gclient-' +
1254 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001255 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001256 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001257 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001258
msb@chromium.org5bde4852009-12-14 16:47:12 +00001259 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001260 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001261 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001262 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001263 return None
1264 return branch
1265
borenet@google.comc3e09d22014-04-10 13:58:18 +00001266 def _Capture(self, args, **kwargs):
Mike Frysinger286fb162019-09-30 03:14:10 +00001267 set_git_dir = 'cwd' not in kwargs
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001268 kwargs.setdefault('cwd', self.checkout_path)
1269 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001270 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001271 env = scm.GIT.ApplyEnvVars(kwargs)
Mike Frysinger286fb162019-09-30 03:14:10 +00001272 # If an explicit cwd isn't set, then default to the .git/ subdir so we get
1273 # stricter behavior. This can be useful in cases of slight corruption --
1274 # we don't accidentally go corrupting parent git checks too. See
1275 # https://crbug.com/1000825 for an example.
1276 if set_git_dir:
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001277 git_dir = os.path.abspath(os.path.join(self.checkout_path, '.git'))
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001278 # Depending on how the .gclient file was defined, self.checkout_path
1279 # might be set to a unicode string, not a regular string; on Windows
1280 # Python2, we can't set env vars to be unicode strings, so we
1281 # forcibly cast the value to a string before setting it.
1282 env.setdefault('GIT_DIR', str(git_dir))
Raul Tambrecd862e32019-05-10 21:19:00 +00001283 ret = subprocess2.check_output(
1284 ['git'] + args, env=env, **kwargs).decode('utf-8')
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001285 if strip:
1286 ret = ret.strip()
Erik Chene16ffff2019-10-14 20:35:53 +00001287 self.Print('Finished running: %s %s' % ('git', ' '.join(args)))
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001288 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001289
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001290 def _Checkout(self, options, ref, force=False, quiet=None):
1291 """Performs a 'git-checkout' operation.
1292
1293 Args:
1294 options: The configured option set
1295 ref: (str) The branch/commit to checkout
Quinten Yearsley925cedb2020-04-13 17:49:39 +00001296 quiet: (bool/None) Whether or not the checkout should pass '--quiet'; if
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001297 'None', the behavior is inferred from 'options.verbose'.
1298 Returns: (str) The output of the checkout operation
1299 """
1300 if quiet is None:
1301 quiet = (not options.verbose)
1302 checkout_args = ['checkout']
1303 if force:
1304 checkout_args.append('--force')
1305 if quiet:
1306 checkout_args.append('--quiet')
1307 checkout_args.append(ref)
1308 return self._Capture(checkout_args)
1309
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001310 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1311 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001312 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemur9a5e3bd2019-04-02 23:37:45 +00001313 # When updating, the ref is modified to be a remote ref .
1314 # (e.g. refs/heads/NAME becomes refs/remotes/REMOTE/NAME).
1315 # Try to reverse that mapping.
1316 original_ref = scm.GIT.RemoteRefToRef(refspec, self.remote)
1317 if original_ref:
1318 refspec = original_ref + ':' + refspec
1319 # When a mirror is configured, it only fetches
1320 # refs/{heads,branch-heads,tags}/*.
1321 # If asked to fetch other refs, we must fetch those directly from the
1322 # repository, and not from the mirror.
1323 if not original_ref.startswith(
1324 ('refs/heads/', 'refs/branch-heads/', 'refs/tags/')):
1325 remote, _ = gclient_utils.SplitUrlRevision(self.url)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001326 fetch_cmd = cfg + [
1327 'fetch',
1328 remote or self.remote,
1329 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001330 if refspec:
1331 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001332
1333 if prune:
1334 fetch_cmd.append('--prune')
1335 if options.verbose:
1336 fetch_cmd.append('--verbose')
danakjd5c0b562019-11-08 17:27:47 +00001337 if not hasattr(options, 'with_tags') or not options.with_tags:
1338 fetch_cmd.append('--no-tags')
dnj@chromium.org680f2172014-06-25 00:39:32 +00001339 elif quiet:
1340 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001341 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001342
Edward Lemur579c9862018-07-13 23:17:51 +00001343 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001344 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1345 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001346 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001347 try:
1348 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1349 options)
1350 self._Run(['config', 'remote.%s.fetch' % self.remote,
1351 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1352 except subprocess2.CalledProcessError as e:
1353 # If exit code was 5, it means we attempted to unset a config that
1354 # didn't exist. Ignore it.
1355 if e.returncode != 5:
1356 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001357 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001358 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001359 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1360 '^\\+refs/branch-heads/\\*:.*$']
1361 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001362 if hasattr(options, 'with_tags') and options.with_tags:
1363 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1364 '+refs/tags/*:refs/tags/*',
1365 '^\\+refs/tags/\\*:.*$']
1366 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001367
John Budorick882c91e2018-07-12 22:11:41 +00001368 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001369 """Attempts to fetch |revision| if not available in local repo.
1370
1371 Returns possibly updated revision."""
Edward Lemure0ba7b82020-03-11 20:31:32 +00001372 if not scm.GIT.IsValidRevision(self.checkout_path, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001373 self._Fetch(options, refspec=revision)
1374 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1375 return revision
1376
Nico Weberc49c88a2020-07-08 17:36:02 +00001377 def _IsRunningUnderRosetta(self):
1378 if sys.platform != 'darwin':
1379 return False
1380 if self._running_under_rosetta is None:
1381 # If we are running under Rosetta, platform.machine() is
1382 # 'x86_64'; we need to use a sysctl to see if we're being
1383 # translated.
1384 import ctypes
1385 libSystem = ctypes.CDLL("libSystem.dylib")
1386 ret = ctypes.c_int(0)
1387 size = ctypes.c_size_t(4)
1388 e = libSystem.sysctlbyname(ctypes.c_char_p(b'sysctl.proc_translated'),
1389 ctypes.byref(ret), ctypes.byref(size), None, 0)
1390 self._running_under_rosetta = e == 0 and ret.value == 1
1391 return self._running_under_rosetta
1392
Edward Lemur24146be2019-08-01 21:44:52 +00001393 def _Run(self, args, options, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001394 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001395 kwargs.setdefault('cwd', self.checkout_path)
Edward Lemur24146be2019-08-01 21:44:52 +00001396 kwargs.setdefault('filter_fn', self.filter)
1397 kwargs.setdefault('show_header', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001398 env = scm.GIT.ApplyEnvVars(kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +00001399
agable@chromium.org772efaf2014-04-01 02:35:44 +00001400 cmd = ['git'] + args
Nico Weberc49c88a2020-07-08 17:36:02 +00001401
1402 if self._IsRunningUnderRosetta():
1403 # We currently only ship an Intel Python binary in depot_tools.
1404 # Intel binaries run under Rosetta on ARM Macs, and by default
1405 # prefer to run their subprocesses as Intel under Rosetta too.
1406 # Intel git running under Rosetta has a bug where it fails to
1407 # clone src.git (rdar://7868319), so until we ship a native
1408 # ARM python3 binary, explicitly use `arch` to let git run
1409 # the native ARM slice instead of the Intel slice.
1410 # TODO(thakis): Remove this again once we ship an arm64 python3
1411 # binary.
Adam Norberga1e15492020-09-11 20:54:18 +00001412 cmd = ['arch', '-arch', 'arm64e', '-arch', 'arm64'] + cmd
Edward Lemur24146be2019-08-01 21:44:52 +00001413 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001414
1415
1416class CipdPackage(object):
1417 """A representation of a single CIPD package."""
1418
John Budorickd3ba72b2018-03-20 12:27:42 -07001419 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001420 self._authority_for_subdir = authority_for_subdir
1421 self._name = name
1422 self._version = version
1423
1424 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001425 def authority_for_subdir(self):
1426 """Whether this package has authority to act on behalf of its subdir.
1427
1428 Some operations should only be performed once per subdirectory. A package
1429 that has authority for its subdirectory is the only package that should
1430 perform such operations.
1431
1432 Returns:
1433 bool; whether this package has subdir authority.
1434 """
1435 return self._authority_for_subdir
1436
1437 @property
1438 def name(self):
1439 return self._name
1440
1441 @property
1442 def version(self):
1443 return self._version
1444
1445
1446class CipdRoot(object):
1447 """A representation of a single CIPD root."""
1448 def __init__(self, root_dir, service_url):
1449 self._all_packages = set()
1450 self._mutator_lock = threading.Lock()
1451 self._packages_by_subdir = collections.defaultdict(list)
1452 self._root_dir = root_dir
1453 self._service_url = service_url
1454
1455 def add_package(self, subdir, package, version):
1456 """Adds a package to this CIPD root.
1457
1458 As far as clients are concerned, this grants both root and subdir authority
1459 to packages arbitrarily. (The implementation grants root authority to the
1460 first package added and subdir authority to the first package added for that
1461 subdir, but clients should not depend on or expect that behavior.)
1462
1463 Args:
1464 subdir: str; relative path to where the package should be installed from
1465 the cipd root directory.
1466 package: str; the cipd package name.
1467 version: str; the cipd package version.
1468 Returns:
1469 CipdPackage; the package that was created and added to this root.
1470 """
1471 with self._mutator_lock:
1472 cipd_package = CipdPackage(
1473 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001474 not self._packages_by_subdir[subdir])
1475 self._all_packages.add(cipd_package)
1476 self._packages_by_subdir[subdir].append(cipd_package)
1477 return cipd_package
1478
1479 def packages(self, subdir):
1480 """Get the list of configured packages for the given subdir."""
1481 return list(self._packages_by_subdir[subdir])
1482
1483 def clobber(self):
1484 """Remove the .cipd directory.
1485
1486 This is useful for forcing ensure to redownload and reinitialize all
1487 packages.
1488 """
1489 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001490 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001491 try:
1492 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1493 except OSError:
1494 if os.path.exists(cipd_cache_dir):
1495 raise
1496
1497 @contextlib.contextmanager
1498 def _create_ensure_file(self):
1499 try:
Edward Lesmes05934952019-12-19 20:38:09 +00001500 contents = '$ParanoidMode CheckPresence\n\n'
1501 for subdir, packages in sorted(self._packages_by_subdir.items()):
1502 contents += '@Subdir %s\n' % subdir
1503 for package in sorted(packages, key=lambda p: p.name):
1504 contents += '%s %s\n' % (package.name, package.version)
1505 contents += '\n'
John Budorick0f7b2002018-01-19 15:46:17 -08001506 ensure_file = None
1507 with tempfile.NamedTemporaryFile(
Edward Lesmes05934952019-12-19 20:38:09 +00001508 suffix='.ensure', delete=False, mode='wb') as ensure_file:
1509 ensure_file.write(contents.encode('utf-8', 'replace'))
John Budorick0f7b2002018-01-19 15:46:17 -08001510 yield ensure_file.name
1511 finally:
1512 if ensure_file is not None and os.path.exists(ensure_file.name):
1513 os.remove(ensure_file.name)
1514
1515 def ensure(self):
1516 """Run `cipd ensure`."""
1517 with self._mutator_lock:
1518 with self._create_ensure_file() as ensure_file:
1519 cmd = [
1520 'cipd', 'ensure',
1521 '-log-level', 'error',
1522 '-root', self.root_dir,
1523 '-ensure-file', ensure_file,
1524 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001525 gclient_utils.CheckCallAndFilter(
1526 cmd, print_stdout=True, show_header=True)
John Budorick0f7b2002018-01-19 15:46:17 -08001527
John Budorickd3ba72b2018-03-20 12:27:42 -07001528 def run(self, command):
1529 if command == 'update':
1530 self.ensure()
1531 elif command == 'revert':
1532 self.clobber()
1533 self.ensure()
1534
John Budorick0f7b2002018-01-19 15:46:17 -08001535 def created_package(self, package):
1536 """Checks whether this root created the given package.
1537
1538 Args:
1539 package: CipdPackage; the package to check.
1540 Returns:
1541 bool; whether this root created the given package.
1542 """
1543 return package in self._all_packages
1544
1545 @property
1546 def root_dir(self):
1547 return self._root_dir
1548
1549 @property
1550 def service_url(self):
1551 return self._service_url
1552
1553
1554class CipdWrapper(SCMWrapper):
1555 """Wrapper for CIPD.
1556
1557 Currently only supports chrome-infra-packages.appspot.com.
1558 """
John Budorick3929e9e2018-02-04 18:18:07 -08001559 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001560
1561 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1562 out_cb=None, root=None, package=None):
1563 super(CipdWrapper, self).__init__(
1564 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1565 out_cb=out_cb)
1566 assert root.created_package(package)
1567 self._package = package
1568 self._root = root
1569
1570 #override
1571 def GetCacheMirror(self):
1572 return None
1573
1574 #override
1575 def GetActualRemoteURL(self, options):
1576 return self._root.service_url
1577
1578 #override
1579 def DoesRemoteURLMatch(self, options):
1580 del options
1581 return True
1582
1583 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001584 """Does nothing.
1585
1586 CIPD packages should be reverted at the root by running
1587 `CipdRoot.run('revert')`.
1588 """
1589 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001590
1591 def diff(self, options, args, file_list):
1592 """CIPD has no notion of diffing."""
1593 pass
1594
1595 def pack(self, options, args, file_list):
1596 """CIPD has no notion of diffing."""
1597 pass
1598
1599 def revinfo(self, options, args, file_list):
1600 """Grab the instance ID."""
1601 try:
1602 tmpdir = tempfile.mkdtemp()
1603 describe_json_path = os.path.join(tmpdir, 'describe.json')
1604 cmd = [
1605 'cipd', 'describe',
1606 self._package.name,
1607 '-log-level', 'error',
1608 '-version', self._package.version,
1609 '-json-output', describe_json_path
1610 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001611 gclient_utils.CheckCallAndFilter(cmd)
John Budorick0f7b2002018-01-19 15:46:17 -08001612 with open(describe_json_path) as f:
1613 describe_json = json.load(f)
1614 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1615 finally:
1616 gclient_utils.rmtree(tmpdir)
1617
1618 def status(self, options, args, file_list):
1619 pass
1620
1621 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001622 """Does nothing.
1623
1624 CIPD packages should be updated at the root by running
1625 `CipdRoot.run('update')`.
1626 """
1627 pass