blob: 0df0da6833ef68c0f78e730fb114cf22b81ce93a [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
348 (e.g. 'refs/heads/master'), but this is not required.
349
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.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000374 e.g. 'refs/heads/master' or 'refs/heads/infra/config'.
375 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 Lemur3acbc742019-05-30 17:57:35 +0000389 elif target_rev.startswith('refs/heads/'):
390 # If |target_rev| is in refs/heads/**, try first to find the corresponding
391 # remote ref for it, since |target_rev| might point to a local ref which
392 # is not up to date with the corresponding remote ref.
393 remote_ref = ''.join(scm.GIT.RefToRemoteRef(target_rev, self.remote))
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000394 self.Print('Trying the corresponding remote ref for %r: %r\n' % (
Edward Lemur3acbc742019-05-30 17:57:35 +0000395 target_rev, remote_ref))
396 if scm.GIT.IsValidRevision(self.checkout_path, remote_ref):
397 target_rev = remote_ref
398 elif not scm.GIT.IsValidRevision(self.checkout_path, target_rev):
399 # Fetch |target_rev| if it's not already available.
400 url, _ = gclient_utils.SplitUrlRevision(self.url)
401 mirror = self._GetMirror(url, options, target_rev)
402 if mirror:
403 rev_type = 'branch' if target_rev.startswith('refs/') else 'hash'
404 self._UpdateMirrorIfNotContains(mirror, options, rev_type, target_rev)
405 self._Fetch(options, refspec=target_rev)
Edward Lemura0ffbe42019-05-01 16:52:18 +0000406
Edward Lemur3acbc742019-05-30 17:57:35 +0000407 self.Print('===Applying patch===')
408 self.Print('Revision to patch is %r @ %r.' % (patch_repo, patch_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000409 self.Print('Current dir is %r' % self.checkout_path)
Edward Lesmesc621b212018-03-21 20:26:56 -0400410 self._Capture(['reset', '--hard'])
danakjd5c0b562019-11-08 17:27:47 +0000411 self._Capture(['fetch', '--no-tags', patch_repo, patch_rev])
Edward Lemurca7d8812018-07-24 17:42:45 +0000412 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400413
Edward Lemur3acbc742019-05-30 17:57:35 +0000414 if not options.rebase_patch_ref:
415 self._Capture(['checkout', patch_rev])
Robert Iannuccic39a7782019-11-01 18:30:33 +0000416 # Adjust base_rev to be the first parent of our checked out patch ref;
417 # This will allow us to correctly extend `file_list`, and will show the
418 # correct file-list to programs which do `git diff --cached` expecting to
419 # see the patch diff.
420 base_rev = self._Capture(['rev-parse', patch_rev+'~'])
421
Edward Lemur3acbc742019-05-30 17:57:35 +0000422 else:
Robert Iannuccic39a7782019-11-01 18:30:33 +0000423 self.Print('Will cherrypick %r .. %r on top of %r.' % (
424 target_rev, patch_rev, base_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000425 try:
426 if scm.GIT.IsAncestor(self.checkout_path, patch_rev, target_rev):
427 # If |patch_rev| is an ancestor of |target_rev|, check it out.
Edward Lemurca7d8812018-07-24 17:42:45 +0000428 self._Capture(['checkout', patch_rev])
429 else:
430 # If a change was uploaded on top of another change, which has already
431 # landed, one of the commits in the cherry-pick range will be
432 # redundant, since it has already landed and its changes incorporated
433 # in the tree.
434 # We pass '--keep-redundant-commits' to ignore those changes.
Edward Lemur3acbc742019-05-30 17:57:35 +0000435 self._Capture(['cherry-pick', target_rev + '..' + patch_rev,
Edward Lemurca7d8812018-07-24 17:42:45 +0000436 '--keep-redundant-commits'])
437
Edward Lemur3acbc742019-05-30 17:57:35 +0000438 except subprocess2.CalledProcessError as e:
439 self.Print('Failed to apply patch.')
440 self.Print('Revision to patch was %r @ %r.' % (patch_repo, patch_rev))
441 self.Print('Tried to cherrypick %r .. %r on top of %r.' % (
442 target_rev, patch_rev, base_rev))
443 self.Print('Current dir is %r' % self.checkout_path)
444 self.Print('git returned non-zero exit status %s:\n%s' % (
Edward Lemur979fa782019-08-13 22:44:05 +0000445 e.returncode, e.stderr.decode('utf-8')))
Edward Lemur3acbc742019-05-30 17:57:35 +0000446 # Print the current status so that developers know what changes caused
447 # the patch failure, since git cherry-pick doesn't show that
448 # information.
449 self.Print(self._Capture(['status']))
450 try:
451 self._Capture(['cherry-pick', '--abort'])
452 except subprocess2.CalledProcessError:
453 pass
454 raise
Edward Lemurca7d8812018-07-24 17:42:45 +0000455
Edward Lemur3acbc742019-05-30 17:57:35 +0000456 if file_list is not None:
457 file_list.extend(self._GetDiffFilenames(base_rev))
Edward Lemurca7d8812018-07-24 17:42:45 +0000458
Edward Lesmesc621b212018-03-21 20:26:56 -0400459 if options.reset_patch_ref:
460 self._Capture(['reset', '--soft', base_rev])
461
msb@chromium.orge28e4982009-09-25 20:51:45 +0000462 def update(self, options, args, file_list):
463 """Runs git to update or transparently checkout the working copy.
464
465 All updated files will be appended to file_list.
466
467 Raises:
468 Error: if can't get URL for relative path.
469 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000470 if args:
471 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
472
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000473 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000474
John Budorick882c91e2018-07-12 22:11:41 +0000475 # If a dependency is not pinned, track the default remote branch.
476 default_rev = 'refs/remotes/%s/master' % self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000477 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000478 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000479 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000480 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000481 # Override the revision number.
482 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000483 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000484 # Check again for a revision in case an initial ref was specified
485 # in the url, for example bla.git@refs/heads/custombranch
486 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000487 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000488 if not revision:
489 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000490
szager@chromium.org8a139702014-06-20 15:55:01 +0000491 if managed:
492 self._DisableHooks()
493
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000494 printed_path = False
495 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000496 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700497 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000498 verbose = ['--verbose']
499 printed_path = True
500
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000501 revision_ref = revision
502 if ':' in revision:
503 revision_ref, _, revision = revision.partition(':')
504
Edward Lesmes8073a502020-04-15 02:11:14 +0000505 if revision_ref.startswith('refs/branch-heads'):
506 options.with_branch_heads = True
507
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000508 mirror = self._GetMirror(url, options, revision_ref)
509 if mirror:
510 url = mirror.mirror_path
Edward Lemurdb5c5ad2019-03-07 16:00:24 +0000511
John Budorick882c91e2018-07-12 22:11:41 +0000512 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
513 if remote_ref:
514 # Rewrite remote refs to their local equivalents.
515 revision = ''.join(remote_ref)
516 rev_type = "branch"
517 elif revision.startswith('refs/'):
518 # Local branch? We probably don't want to support, since DEPS should
519 # always specify branches as they are in the upstream repo.
520 rev_type = "branch"
521 else:
522 # hash is also a tag, only make a distinction at checkout
523 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000524
primiano@chromium.org1c127382015-02-17 11:15:40 +0000525 # If we are going to introduce a new project, there is a possibility that
526 # we are syncing back to a state where the project was originally a
527 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
528 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
529 # In such case, we might have a backup of the former .git folder, which can
530 # be used to avoid re-fetching the entire repo again (useful for bisects).
531 backup_dir = self.GetGitBackupDirPath()
532 target_dir = os.path.join(self.checkout_path, '.git')
533 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
534 gclient_utils.safe_makedirs(self.checkout_path)
535 os.rename(backup_dir, target_dir)
536 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800537 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000538
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000539 if (not os.path.exists(self.checkout_path) or
540 (os.path.isdir(self.checkout_path) and
541 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000542 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000543 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000544 try:
John Budorick882c91e2018-07-12 22:11:41 +0000545 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000546 except subprocess2.CalledProcessError:
547 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000548 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000549 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800550 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20 +0000551 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambrec2f74c12019-03-19 05:55:53 +0000552 file_list.extend(
Edward Lemur979fa782019-08-13 22:44:05 +0000553 [os.path.join(self.checkout_path, f) for f in files])
John Budorick21a51b32018-09-19 19:39:20 +0000554 if mirror:
555 self._Capture(
556 ['remote', 'set-url', '--push', 'origin', mirror.url])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000557 if not verbose:
558 # Make the output a little prettier. It's nice to have some whitespace
559 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000560 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000561 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000562
John Budorick21a51b32018-09-19 19:39:20 +0000563 if mirror:
564 self._Capture(
565 ['remote', 'set-url', '--push', 'origin', mirror.url])
566
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000567 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000568 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000569 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
570 return self._Capture(['rev-parse', '--verify', 'HEAD'])
571
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000572 self._maybe_break_locks(options)
573
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000574 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000575 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000576
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000577 # See if the url has changed (the unittests use git://foo for the url, let
578 # that through).
579 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
580 return_early = False
581 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
582 # unit test pass. (and update the comment above)
583 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
584 # This allows devs to use experimental repos which have a different url
585 # but whose branch(s) are the same as official repos.
Raul Tambrecd862e32019-05-10 21:19:00 +0000586 if (current_url.rstrip('/') != url.rstrip('/') and url != 'git://foo' and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000587 subprocess2.capture(
Aaron Gableac9b0f32019-04-18 17:38:37 +0000588 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000589 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000590 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000591 if not (options.force or options.reset):
592 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700593 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000594 # Switch over to the new upstream
595 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000596 if mirror:
597 with open(os.path.join(
598 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
599 'w') as fh:
600 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000601 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
602 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000603
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000604 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000605 else:
John Budorick882c91e2018-07-12 22:11:41 +0000606 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000607
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000608 if return_early:
609 return self._Capture(['rev-parse', '--verify', 'HEAD'])
610
msb@chromium.org5bde4852009-12-14 16:47:12 +0000611 cur_branch = self._GetCurrentBranch()
612
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000613 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000614 # 0) HEAD is detached. Probably from our initial clone.
615 # - make sure HEAD is contained by a named ref, then update.
616 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700617 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000618 # - try to rebase onto the new hash or branch
619 # 2) current branch is tracking a remote branch with local committed
620 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000621 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000622 # 3) current branch is tracking a remote branch w/or w/out changes, and
623 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000624 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000625 # 4) current branch is tracking a remote branch, but DEPS switches to a
626 # different remote branch, and
627 # a) current branch has no local changes, and --force:
628 # - checkout new branch
629 # b) current branch has local changes, and --force and --reset:
630 # - checkout new branch
631 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000632
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000633 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
634 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000635 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
636 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000637 if cur_branch is None:
638 upstream_branch = None
639 current_type = "detached"
640 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000641 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000642 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
643 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
644 current_type = "hash"
645 logging.debug("Current branch is not tracking an upstream (remote)"
646 " branch.")
647 elif upstream_branch.startswith('refs/remotes'):
648 current_type = "branch"
649 else:
650 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000651
Edward Lemur579c9862018-07-13 23:17:51 +0000652 self._SetFetchConfig(options)
Edward Lemur579c9862018-07-13 23:17:51 +0000653
Michael Spang73fac912019-03-08 18:44:19 +0000654 # Fetch upstream if we don't already have |revision|.
John Budorick882c91e2018-07-12 22:11:41 +0000655 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
Michael Spang73fac912019-03-08 18:44:19 +0000656 self._Fetch(options, prune=options.force)
657
658 if not scm.GIT.IsValidRevision(self.checkout_path, revision,
659 sha_only=True):
660 # Update the remotes first so we have all the refs.
661 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
662 cwd=self.checkout_path)
663 if verbose:
664 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000665
John Budorick882c91e2018-07-12 22:11:41 +0000666 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200667
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000668 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000669 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000670 target = 'HEAD'
671 if options.upstream and upstream_branch:
672 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800673 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000674
msb@chromium.org786fb682010-06-02 15:16:23 +0000675 if current_type == 'detached':
676 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800677 # We just did a Scrub, this is as clean as it's going to get. In
678 # particular if HEAD is a commit that contains two versions of the same
679 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
680 # to actually "Clean" the checkout; that commit is uncheckoutable on this
681 # system. The best we can do is carry forward to the checkout step.
682 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000683 self._CheckClean(revision)
684 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000685 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000686 self.Print('Up-to-date; skipping checkout.')
687 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000688 # 'git checkout' may need to overwrite existing untracked files. Allow
689 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000690 self._Checkout(
691 options,
John Budorick882c91e2018-07-12 22:11:41 +0000692 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000693 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000694 quiet=True,
695 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000696 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000697 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000698 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000699 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700700 # Can't find a merge-base since we don't know our upstream. That makes
701 # this command VERY likely to produce a rebase failure. For now we
702 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000703 upstream_branch = self.remote
704 if options.revision or deps_revision:
705 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700706 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700707 printed_path=printed_path, merge=options.merge)
708 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000709 elif rev_type == 'hash':
710 # case 2
711 self._AttemptRebase(upstream_branch, file_list, options,
712 newbase=revision, printed_path=printed_path,
713 merge=options.merge)
714 printed_path = True
715 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000716 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000717 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000718 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000719 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000720 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000721 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000722 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000723 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
724 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000725 force_switch = False
726 if options.force:
727 try:
John Budorick882c91e2018-07-12 22:11:41 +0000728 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000729 # case 4a
730 force_switch = True
731 except gclient_utils.Error as e:
732 if options.reset:
733 # case 4b
734 force_switch = True
735 else:
736 switch_error = '%s\n%s' % (e.message, switch_error)
737 if force_switch:
738 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000739 (upstream_branch, new_base))
740 switch_branch = 'gclient_' + remote_ref[1]
741 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000742 self._Checkout(options, switch_branch, force=True, quiet=True)
743 else:
744 # case 4c
745 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000746 else:
747 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800748 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000749 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000750 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000751 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000752 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000753 if options.merge:
754 merge_args.append('--ff')
755 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000756 merge_args.append('--ff-only')
757 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000758 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000759 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700760 rebase_files = []
Edward Lemur979fa782019-08-13 22:44:05 +0000761 if re.match(b'fatal: Not possible to fast-forward, aborting.',
762 e.stderr):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000763 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000764 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700765 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000766 printed_path = True
767 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000768 if not options.auto_rebase:
769 try:
770 action = self._AskForData(
771 'Cannot %s, attempt to rebase? '
772 '(y)es / (q)uit / (s)kip : ' %
773 ('merge' if options.merge else 'fast-forward merge'),
774 options)
775 except ValueError:
776 raise gclient_utils.Error('Invalid Character')
777 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700778 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000779 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000780 printed_path = True
781 break
782 elif re.match(r'quit|q', action, re.I):
783 raise gclient_utils.Error("Can't fast-forward, please merge or "
784 "rebase manually.\n"
785 "cd %s && git " % self.checkout_path
786 + "rebase %s" % upstream_branch)
787 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000788 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000789 return
790 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000791 self.Print('Input not recognized')
Edward Lemur979fa782019-08-13 22:44:05 +0000792 elif re.match(b"error: Your local changes to '.*' would be "
793 b"overwritten by merge. Aborting.\nPlease, commit your "
794 b"changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000795 e.stderr):
796 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000797 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700798 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000799 printed_path = True
Edward Lemur979fa782019-08-13 22:44:05 +0000800 raise gclient_utils.Error(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000801 else:
802 # Some other problem happened with the merge
803 logging.error("Error during fast-forward merge in %s!" % self.relpath)
Edward Lemur979fa782019-08-13 22:44:05 +0000804 self.Print(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000805 raise
806 else:
807 # Fast-forward merge was successful
808 if not re.match('Already up-to-date.', merge_output) or verbose:
809 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700810 self.Print('_____ %s at %s' % (self.relpath, revision),
811 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000812 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000813 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000814 if not verbose:
815 # Make the output a little prettier. It's nice to have some
816 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000817 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000818
agablec3937b92016-10-25 10:13:03 -0700819 if file_list is not None:
820 file_list.extend(
821 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000822
823 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000824 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700825 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000826 '\nConflict while rebasing this branch.\n'
827 'Fix the conflict and run gclient again.\n'
828 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700829 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000830
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000831 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000832 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
833 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000834
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000835 # If --reset and --delete_unversioned_trees are specified, remove any
836 # untracked directories.
837 if options.reset and options.delete_unversioned_trees:
838 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
839 # merge-base by default), so doesn't include untracked files. So we use
840 # 'git ls-files --directory --others --exclude-standard' here directly.
841 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800842 ['-c', 'core.quotePath=false', 'ls-files',
843 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000844 self.checkout_path)
845 for path in (p for p in paths.splitlines() if p.endswith('/')):
846 full_path = os.path.join(self.checkout_path, path)
847 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000848 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000849 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000850
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000851 return self._Capture(['rev-parse', '--verify', 'HEAD'])
852
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000853 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000854 """Reverts local modifications.
855
856 All reverted files will be appended to file_list.
857 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000858 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000859 # revert won't work if the directory doesn't exist. It needs to
860 # checkout instead.
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000861 self.Print('_____ %s is missing, syncing instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000862 # Don't reuse the args.
863 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000864
865 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000866 if options.upstream:
867 if self._GetCurrentBranch():
868 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
869 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000870 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000871 if not deps_revision:
872 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000873 if deps_revision.startswith('refs/heads/'):
874 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700875 try:
876 deps_revision = self.GetUsableRev(deps_revision, options)
877 except NoUsableRevError as e:
878 # If the DEPS entry's url and hash changed, try to update the origin.
879 # See also http://crbug.com/520067.
John Budorickd94f8ea2020-03-27 15:55:24 +0000880 logging.warning(
881 "Couldn't find usable revision, will retrying to update instead: %s",
smutae7ea312016-07-18 11:59:41 -0700882 e.message)
883 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000884
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000885 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800886 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000887
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800888 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000889 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000890
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000891 if file_list is not None:
892 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
893
894 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000895 """Returns revision"""
896 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000897
msb@chromium.orge28e4982009-09-25 20:51:45 +0000898 def runhooks(self, options, args, file_list):
899 self.status(options, args, file_list)
900
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000901 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000902 """Display status information."""
903 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000904 self.Print('________ couldn\'t run status in %s:\n'
905 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000906 else:
Anthony Politobb457342019-11-15 22:26:01 +0000907 merge_base = []
908 if self.url:
909 _, base_rev = gclient_utils.SplitUrlRevision(self.url)
910 if base_rev:
911 merge_base = [base_rev]
Aaron Gablef4068aa2017-12-12 15:14:09 -0800912 self._Run(
913 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
Edward Lemur24146be2019-08-01 21:44:52 +0000914 options, always_show_header=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000915 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800916 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000917 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000918
smutae7ea312016-07-18 11:59:41 -0700919 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700920 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700921 sha1 = None
922 if not os.path.isdir(self.checkout_path):
923 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800924 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700925
926 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
927 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700928 else:
agable41e3a6c2016-10-20 11:36:56 -0700929 # May exist in origin, but we don't have it yet, so fetch and look
930 # again.
931 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700932 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
933 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700934
935 if not sha1:
936 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800937 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700938
939 return sha1
940
primiano@chromium.org1c127382015-02-17 11:15:40 +0000941 def GetGitBackupDirPath(self):
942 """Returns the path where the .git folder for the current project can be
943 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
944 return os.path.join(self._root_dir,
945 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
946
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000947 def _GetMirror(self, url, options, revision_ref=None):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000948 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000949 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000950 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000951 mirror_kwargs = {
952 'print_func': self.filter,
John Budorick882c91e2018-07-12 22:11:41 +0000953 'refs': []
hinoka@google.comb1b54572014-04-16 22:29:23 +0000954 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000955 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
956 mirror_kwargs['refs'].append('refs/branch-heads/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000957 elif revision_ref and revision_ref.startswith('refs/branch-heads/'):
958 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000959 if hasattr(options, 'with_tags') and options.with_tags:
960 mirror_kwargs['refs'].append('refs/tags/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000961 elif revision_ref and revision_ref.startswith('refs/tags/'):
962 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000963 return git_cache.Mirror(url, **mirror_kwargs)
964
John Budorick882c91e2018-07-12 22:11:41 +0000965 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800966 """Update a git mirror by fetching the latest commits from the remote,
967 unless mirror already contains revision whose type is sha1 hash.
968 """
John Budorick882c91e2018-07-12 22:11:41 +0000969 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800970 if options.verbose:
971 self.Print('skipping mirror update, it has rev=%s already' % revision,
972 timestamp=False)
973 return
974
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000975 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000976 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000977 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000978 depth = 10
979 else:
980 depth = 10000
981 else:
982 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000983 mirror.populate(verbose=options.verbose,
984 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000985 depth=depth,
Vadim Shtayura08049e22017-10-11 00:14:52 +0000986 lock_timeout=getattr(options, 'lock_timeout', 0))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000987
John Budorick882c91e2018-07-12 22:11:41 +0000988 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000989 """Clone a git repository from the given URL.
990
msb@chromium.org786fb682010-06-02 15:16:23 +0000991 Once we've cloned the repo, we checkout a working branch if the specified
992 revision is a branch head. If it is a tag or a specific commit, then we
993 leave HEAD detached as it makes future updates simpler -- in this case the
994 user should first create a new branch or switch to an existing branch before
995 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000996 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000997 # git clone doesn't seem to insert a newline properly before printing
998 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000999 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +00001000 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +00001001 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001002 if self.cache_dir:
1003 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001004 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001005 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001006 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +00001007 # If the parent directory does not exist, Git clone on Windows will not
1008 # create it, so we need to do it manually.
1009 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001010 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001011
1012 template_dir = None
1013 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +00001014 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001015 # In the case of a subproject, the pinned sha is not necessarily the
1016 # head of the remote branch (so we can't just use --depth=N). Instead,
1017 # we tell git to fetch all the remote objects from SHA..HEAD by means of
1018 # a template git dir which has a 'shallow' file pointing to the sha.
1019 template_dir = tempfile.mkdtemp(
1020 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
1021 dir=parent_dir)
1022 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
1023 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
1024 template_file.write(revision)
1025 clone_cmd.append('--template=' + template_dir)
1026 else:
1027 # Otherwise, we're just interested in the HEAD. Just use --depth.
1028 clone_cmd.append('--depth=1')
1029
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001030 tmp_dir = tempfile.mkdtemp(
1031 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
1032 dir=parent_dir)
1033 try:
1034 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001035 if self.print_outbuf:
1036 print_stdout = True
Edward Lemur24146be2019-08-01 21:44:52 +00001037 filter_fn = None
Edward Lemur231f5ea2018-01-31 19:02:36 +01001038 else:
1039 print_stdout = False
Edward Lemur24146be2019-08-01 21:44:52 +00001040 filter_fn = self.filter
Edward Lemur231f5ea2018-01-31 19:02:36 +01001041 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
Edward Lemur24146be2019-08-01 21:44:52 +00001042 print_stdout=print_stdout, filter_fn=filter_fn)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001043 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +00001044 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
1045 os.path.join(self.checkout_path, '.git'))
Edward Lesmesd4e20f22020-07-15 21:11:08 +00001046 # TODO(https://github.com/git-for-windows/git/issues/2569): Remove once
1047 # fixed.
1048 if sys.platform.startswith('win'):
1049 try:
1050 self._Run(['config', '--unset', 'core.worktree'], options,
1051 cwd=self.checkout_path)
1052 except subprocess2.CalledProcessError:
1053 pass
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001054 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001055 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001056 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001057 finally:
1058 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001059 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001060 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001061 if template_dir:
1062 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +00001063 self._SetFetchConfig(options)
1064 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001065 revision = self._AutoFetchRef(options, revision)
1066 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1067 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +00001068 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +00001069 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001070 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +00001071 ('Checked out %s to a detached HEAD. Before making any commits\n'
1072 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1073 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +00001074 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001075
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001076 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001077 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001078 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001079 raise gclient_utils.Error("Background task requires input. Rerun "
1080 "gclient with --jobs=1 so that\n"
1081 "interaction is possible.")
Edward Lesmesae3586b2020-03-23 21:21:14 +00001082 return gclient_utils.AskForData(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001083
1084
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001085 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001086 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001087 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001088 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001089 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001090 revision = upstream
1091 if newbase:
1092 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001093 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001094 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001095 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001096 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001097 printed_path = True
1098 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001099 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001100
1101 if merge:
1102 merge_output = self._Capture(['merge', revision])
1103 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001104 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001105 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001106
1107 # Build the rebase command here using the args
1108 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1109 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001110 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001111 rebase_cmd.append('--verbose')
1112 if newbase:
1113 rebase_cmd.extend(['--onto', newbase])
1114 rebase_cmd.append(upstream)
1115 if branch:
1116 rebase_cmd.append(branch)
1117
1118 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001119 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
Raul Tambreb946b232019-03-26 14:48:46 +00001120 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001121 if (re.match(br'cannot rebase: you have unstaged changes', e.stderr) or
1122 re.match(br'cannot rebase: your index contains uncommitted changes',
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001123 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001124 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001125 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001126 'Cannot rebase because of unstaged changes.\n'
1127 '\'git reset --hard HEAD\' ?\n'
1128 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001129 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001130 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001131 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001132 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001133 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001134 break
1135 elif re.match(r'quit|q', rebase_action, re.I):
1136 raise gclient_utils.Error("Please merge or rebase manually\n"
1137 "cd %s && git " % self.checkout_path
1138 + "%s" % ' '.join(rebase_cmd))
1139 elif re.match(r'show|s', rebase_action, re.I):
Edward Lemur979fa782019-08-13 22:44:05 +00001140 self.Print('%s' % e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001141 continue
1142 else:
1143 gclient_utils.Error("Input not recognized")
1144 continue
Edward Lemur979fa782019-08-13 22:44:05 +00001145 elif re.search(br'^CONFLICT', e.stdout, re.M):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001146 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1147 "Fix the conflict and run gclient again.\n"
1148 "See 'man git-rebase' for details.\n")
1149 else:
Edward Lemur979fa782019-08-13 22:44:05 +00001150 self.Print(e.stdout.decode('utf-8').strip())
1151 self.Print('Rebase produced error output:\n%s' %
1152 e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001153 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1154 "manually.\ncd %s && git " %
1155 self.checkout_path
1156 + "%s" % ' '.join(rebase_cmd))
1157
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001158 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001159 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001160 # Make the output a little prettier. It's nice to have some
1161 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001162 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001163
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001164 @staticmethod
1165 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001166 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1167 if not ok:
1168 raise gclient_utils.Error('git version %s < minimum required %s' %
1169 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001170
John Budorick882c91e2018-07-12 22:11:41 +00001171 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001172 # Special case handling if all 3 conditions are met:
1173 # * the mirros have recently changed, but deps destination remains same,
1174 # * the git histories of mirrors are conflicting.
1175 # * git cache is used
1176 # This manifests itself in current checkout having invalid HEAD commit on
1177 # most git operations. Since git cache is used, just deleted the .git
1178 # folder, and re-create it by cloning.
1179 try:
1180 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1181 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001182 if (b'fatal: bad object HEAD' in e.stderr
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001183 and self.cache_dir and self.cache_dir in url):
1184 self.Print((
1185 'Likely due to DEPS change with git cache_dir, '
1186 'the current commit points to no longer existing object.\n'
1187 '%s' % e)
1188 )
1189 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001190 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001191 else:
1192 raise
1193
msb@chromium.org786fb682010-06-02 15:16:23 +00001194 def _IsRebasing(self):
1195 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1196 # have a plumbing command to determine whether a rebase is in progress, so
1197 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1198 g = os.path.join(self.checkout_path, '.git')
1199 return (
1200 os.path.isdir(os.path.join(g, "rebase-merge")) or
1201 os.path.isdir(os.path.join(g, "rebase-apply")))
1202
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001203 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001204 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1205 if os.path.exists(lockfile):
1206 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001207 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001208 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1209 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001210 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001211
msb@chromium.org786fb682010-06-02 15:16:23 +00001212 # Make sure the tree is clean; see git-rebase.sh for reference
1213 try:
1214 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001215 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001216 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001217 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001218 '\tYou have unstaged changes.\n'
1219 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001220 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001221 try:
1222 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001223 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001224 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001225 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001226 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001227 '\tYour index contains uncommitted changes\n'
1228 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001229 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001230
agable83faed02016-10-24 14:37:10 -07001231 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001232 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1233 # reference by a commit). If not, error out -- most likely a rebase is
1234 # in progress, try to detect so we can give a better error.
1235 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001236 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1237 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001238 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001239 # Commit is not contained by any rev. See if the user is rebasing:
1240 if self._IsRebasing():
1241 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001242 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001243 '\tAlready in a conflict, i.e. (no branch).\n'
1244 '\tFix the conflict and run gclient again.\n'
1245 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1246 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001247 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001248 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001249 name = ('saved-by-gclient-' +
1250 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001251 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001252 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001253 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001254
msb@chromium.org5bde4852009-12-14 16:47:12 +00001255 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001256 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001257 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001258 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001259 return None
1260 return branch
1261
borenet@google.comc3e09d22014-04-10 13:58:18 +00001262 def _Capture(self, args, **kwargs):
Mike Frysinger286fb162019-09-30 03:14:10 +00001263 set_git_dir = 'cwd' not in kwargs
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001264 kwargs.setdefault('cwd', self.checkout_path)
1265 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001266 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001267 env = scm.GIT.ApplyEnvVars(kwargs)
Mike Frysinger286fb162019-09-30 03:14:10 +00001268 # If an explicit cwd isn't set, then default to the .git/ subdir so we get
1269 # stricter behavior. This can be useful in cases of slight corruption --
1270 # we don't accidentally go corrupting parent git checks too. See
1271 # https://crbug.com/1000825 for an example.
1272 if set_git_dir:
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001273 git_dir = os.path.abspath(os.path.join(self.checkout_path, '.git'))
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001274 # Depending on how the .gclient file was defined, self.checkout_path
1275 # might be set to a unicode string, not a regular string; on Windows
1276 # Python2, we can't set env vars to be unicode strings, so we
1277 # forcibly cast the value to a string before setting it.
1278 env.setdefault('GIT_DIR', str(git_dir))
Raul Tambrecd862e32019-05-10 21:19:00 +00001279 ret = subprocess2.check_output(
1280 ['git'] + args, env=env, **kwargs).decode('utf-8')
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001281 if strip:
1282 ret = ret.strip()
Erik Chene16ffff2019-10-14 20:35:53 +00001283 self.Print('Finished running: %s %s' % ('git', ' '.join(args)))
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001284 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001285
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001286 def _Checkout(self, options, ref, force=False, quiet=None):
1287 """Performs a 'git-checkout' operation.
1288
1289 Args:
1290 options: The configured option set
1291 ref: (str) The branch/commit to checkout
Quinten Yearsley925cedb2020-04-13 17:49:39 +00001292 quiet: (bool/None) Whether or not the checkout should pass '--quiet'; if
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001293 'None', the behavior is inferred from 'options.verbose'.
1294 Returns: (str) The output of the checkout operation
1295 """
1296 if quiet is None:
1297 quiet = (not options.verbose)
1298 checkout_args = ['checkout']
1299 if force:
1300 checkout_args.append('--force')
1301 if quiet:
1302 checkout_args.append('--quiet')
1303 checkout_args.append(ref)
1304 return self._Capture(checkout_args)
1305
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001306 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1307 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001308 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemur9a5e3bd2019-04-02 23:37:45 +00001309 # When updating, the ref is modified to be a remote ref .
1310 # (e.g. refs/heads/NAME becomes refs/remotes/REMOTE/NAME).
1311 # Try to reverse that mapping.
1312 original_ref = scm.GIT.RemoteRefToRef(refspec, self.remote)
1313 if original_ref:
1314 refspec = original_ref + ':' + refspec
1315 # When a mirror is configured, it only fetches
1316 # refs/{heads,branch-heads,tags}/*.
1317 # If asked to fetch other refs, we must fetch those directly from the
1318 # repository, and not from the mirror.
1319 if not original_ref.startswith(
1320 ('refs/heads/', 'refs/branch-heads/', 'refs/tags/')):
1321 remote, _ = gclient_utils.SplitUrlRevision(self.url)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001322 fetch_cmd = cfg + [
1323 'fetch',
1324 remote or self.remote,
1325 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001326 if refspec:
1327 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001328
1329 if prune:
1330 fetch_cmd.append('--prune')
1331 if options.verbose:
1332 fetch_cmd.append('--verbose')
danakjd5c0b562019-11-08 17:27:47 +00001333 if not hasattr(options, 'with_tags') or not options.with_tags:
1334 fetch_cmd.append('--no-tags')
dnj@chromium.org680f2172014-06-25 00:39:32 +00001335 elif quiet:
1336 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001337 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001338
Edward Lemur579c9862018-07-13 23:17:51 +00001339 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001340 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1341 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001342 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001343 try:
1344 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1345 options)
1346 self._Run(['config', 'remote.%s.fetch' % self.remote,
1347 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1348 except subprocess2.CalledProcessError as e:
1349 # If exit code was 5, it means we attempted to unset a config that
1350 # didn't exist. Ignore it.
1351 if e.returncode != 5:
1352 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001353 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001354 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001355 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1356 '^\\+refs/branch-heads/\\*:.*$']
1357 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001358 if hasattr(options, 'with_tags') and options.with_tags:
1359 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1360 '+refs/tags/*:refs/tags/*',
1361 '^\\+refs/tags/\\*:.*$']
1362 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001363
John Budorick882c91e2018-07-12 22:11:41 +00001364 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001365 """Attempts to fetch |revision| if not available in local repo.
1366
1367 Returns possibly updated revision."""
Edward Lemure0ba7b82020-03-11 20:31:32 +00001368 if not scm.GIT.IsValidRevision(self.checkout_path, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001369 self._Fetch(options, refspec=revision)
1370 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1371 return revision
1372
Nico Weberc49c88a2020-07-08 17:36:02 +00001373 def _IsRunningUnderRosetta(self):
1374 if sys.platform != 'darwin':
1375 return False
1376 if self._running_under_rosetta is None:
1377 # If we are running under Rosetta, platform.machine() is
1378 # 'x86_64'; we need to use a sysctl to see if we're being
1379 # translated.
1380 import ctypes
1381 libSystem = ctypes.CDLL("libSystem.dylib")
1382 ret = ctypes.c_int(0)
1383 size = ctypes.c_size_t(4)
1384 e = libSystem.sysctlbyname(ctypes.c_char_p(b'sysctl.proc_translated'),
1385 ctypes.byref(ret), ctypes.byref(size), None, 0)
1386 self._running_under_rosetta = e == 0 and ret.value == 1
1387 return self._running_under_rosetta
1388
Edward Lemur24146be2019-08-01 21:44:52 +00001389 def _Run(self, args, options, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001390 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001391 kwargs.setdefault('cwd', self.checkout_path)
Edward Lemur24146be2019-08-01 21:44:52 +00001392 kwargs.setdefault('filter_fn', self.filter)
1393 kwargs.setdefault('show_header', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001394 env = scm.GIT.ApplyEnvVars(kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +00001395
agable@chromium.org772efaf2014-04-01 02:35:44 +00001396 cmd = ['git'] + args
Nico Weberc49c88a2020-07-08 17:36:02 +00001397
1398 if self._IsRunningUnderRosetta():
1399 # We currently only ship an Intel Python binary in depot_tools.
1400 # Intel binaries run under Rosetta on ARM Macs, and by default
1401 # prefer to run their subprocesses as Intel under Rosetta too.
1402 # Intel git running under Rosetta has a bug where it fails to
1403 # clone src.git (rdar://7868319), so until we ship a native
1404 # ARM python3 binary, explicitly use `arch` to let git run
1405 # the native ARM slice instead of the Intel slice.
1406 # TODO(thakis): Remove this again once we ship an arm64 python3
1407 # binary.
1408 cmd = ['arch', '-arch', 'arm64'] + cmd
Edward Lemur24146be2019-08-01 21:44:52 +00001409 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001410
1411
1412class CipdPackage(object):
1413 """A representation of a single CIPD package."""
1414
John Budorickd3ba72b2018-03-20 12:27:42 -07001415 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001416 self._authority_for_subdir = authority_for_subdir
1417 self._name = name
1418 self._version = version
1419
1420 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001421 def authority_for_subdir(self):
1422 """Whether this package has authority to act on behalf of its subdir.
1423
1424 Some operations should only be performed once per subdirectory. A package
1425 that has authority for its subdirectory is the only package that should
1426 perform such operations.
1427
1428 Returns:
1429 bool; whether this package has subdir authority.
1430 """
1431 return self._authority_for_subdir
1432
1433 @property
1434 def name(self):
1435 return self._name
1436
1437 @property
1438 def version(self):
1439 return self._version
1440
1441
1442class CipdRoot(object):
1443 """A representation of a single CIPD root."""
1444 def __init__(self, root_dir, service_url):
1445 self._all_packages = set()
1446 self._mutator_lock = threading.Lock()
1447 self._packages_by_subdir = collections.defaultdict(list)
1448 self._root_dir = root_dir
1449 self._service_url = service_url
1450
1451 def add_package(self, subdir, package, version):
1452 """Adds a package to this CIPD root.
1453
1454 As far as clients are concerned, this grants both root and subdir authority
1455 to packages arbitrarily. (The implementation grants root authority to the
1456 first package added and subdir authority to the first package added for that
1457 subdir, but clients should not depend on or expect that behavior.)
1458
1459 Args:
1460 subdir: str; relative path to where the package should be installed from
1461 the cipd root directory.
1462 package: str; the cipd package name.
1463 version: str; the cipd package version.
1464 Returns:
1465 CipdPackage; the package that was created and added to this root.
1466 """
1467 with self._mutator_lock:
1468 cipd_package = CipdPackage(
1469 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001470 not self._packages_by_subdir[subdir])
1471 self._all_packages.add(cipd_package)
1472 self._packages_by_subdir[subdir].append(cipd_package)
1473 return cipd_package
1474
1475 def packages(self, subdir):
1476 """Get the list of configured packages for the given subdir."""
1477 return list(self._packages_by_subdir[subdir])
1478
1479 def clobber(self):
1480 """Remove the .cipd directory.
1481
1482 This is useful for forcing ensure to redownload and reinitialize all
1483 packages.
1484 """
1485 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001486 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001487 try:
1488 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1489 except OSError:
1490 if os.path.exists(cipd_cache_dir):
1491 raise
1492
1493 @contextlib.contextmanager
1494 def _create_ensure_file(self):
1495 try:
Edward Lesmes05934952019-12-19 20:38:09 +00001496 contents = '$ParanoidMode CheckPresence\n\n'
1497 for subdir, packages in sorted(self._packages_by_subdir.items()):
1498 contents += '@Subdir %s\n' % subdir
1499 for package in sorted(packages, key=lambda p: p.name):
1500 contents += '%s %s\n' % (package.name, package.version)
1501 contents += '\n'
John Budorick0f7b2002018-01-19 15:46:17 -08001502 ensure_file = None
1503 with tempfile.NamedTemporaryFile(
Edward Lesmes05934952019-12-19 20:38:09 +00001504 suffix='.ensure', delete=False, mode='wb') as ensure_file:
1505 ensure_file.write(contents.encode('utf-8', 'replace'))
John Budorick0f7b2002018-01-19 15:46:17 -08001506 yield ensure_file.name
1507 finally:
1508 if ensure_file is not None and os.path.exists(ensure_file.name):
1509 os.remove(ensure_file.name)
1510
1511 def ensure(self):
1512 """Run `cipd ensure`."""
1513 with self._mutator_lock:
1514 with self._create_ensure_file() as ensure_file:
1515 cmd = [
1516 'cipd', 'ensure',
1517 '-log-level', 'error',
1518 '-root', self.root_dir,
1519 '-ensure-file', ensure_file,
1520 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001521 gclient_utils.CheckCallAndFilter(
1522 cmd, print_stdout=True, show_header=True)
John Budorick0f7b2002018-01-19 15:46:17 -08001523
John Budorickd3ba72b2018-03-20 12:27:42 -07001524 def run(self, command):
1525 if command == 'update':
1526 self.ensure()
1527 elif command == 'revert':
1528 self.clobber()
1529 self.ensure()
1530
John Budorick0f7b2002018-01-19 15:46:17 -08001531 def created_package(self, package):
1532 """Checks whether this root created the given package.
1533
1534 Args:
1535 package: CipdPackage; the package to check.
1536 Returns:
1537 bool; whether this root created the given package.
1538 """
1539 return package in self._all_packages
1540
1541 @property
1542 def root_dir(self):
1543 return self._root_dir
1544
1545 @property
1546 def service_url(self):
1547 return self._service_url
1548
1549
1550class CipdWrapper(SCMWrapper):
1551 """Wrapper for CIPD.
1552
1553 Currently only supports chrome-infra-packages.appspot.com.
1554 """
John Budorick3929e9e2018-02-04 18:18:07 -08001555 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001556
1557 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1558 out_cb=None, root=None, package=None):
1559 super(CipdWrapper, self).__init__(
1560 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1561 out_cb=out_cb)
1562 assert root.created_package(package)
1563 self._package = package
1564 self._root = root
1565
1566 #override
1567 def GetCacheMirror(self):
1568 return None
1569
1570 #override
1571 def GetActualRemoteURL(self, options):
1572 return self._root.service_url
1573
1574 #override
1575 def DoesRemoteURLMatch(self, options):
1576 del options
1577 return True
1578
1579 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001580 """Does nothing.
1581
1582 CIPD packages should be reverted at the root by running
1583 `CipdRoot.run('revert')`.
1584 """
1585 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001586
1587 def diff(self, options, args, file_list):
1588 """CIPD has no notion of diffing."""
1589 pass
1590
1591 def pack(self, options, args, file_list):
1592 """CIPD has no notion of diffing."""
1593 pass
1594
1595 def revinfo(self, options, args, file_list):
1596 """Grab the instance ID."""
1597 try:
1598 tmpdir = tempfile.mkdtemp()
1599 describe_json_path = os.path.join(tmpdir, 'describe.json')
1600 cmd = [
1601 'cipd', 'describe',
1602 self._package.name,
1603 '-log-level', 'error',
1604 '-version', self._package.version,
1605 '-json-output', describe_json_path
1606 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001607 gclient_utils.CheckCallAndFilter(cmd)
John Budorick0f7b2002018-01-19 15:46:17 -08001608 with open(describe_json_path) as f:
1609 describe_json = json.load(f)
1610 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1611 finally:
1612 gclient_utils.rmtree(tmpdir)
1613
1614 def status(self, options, args, file_list):
1615 pass
1616
1617 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001618 """Does nothing.
1619
1620 CIPD packages should be updated at the root by running
1621 `CipdRoot.run('update')`.
1622 """
1623 pass