blob: 80519d9bbd0dd86f5d62cc852eac4b89c22ea77f [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
Tomasz Wiszkowskid4e66882021-08-19 21:35:09 +000015import platform
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000016import posixpath
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000017import re
maruel@chromium.org90541732011-04-01 17:54:18 +000018import sys
ilevy@chromium.org3534aa52013-07-20 01:58:08 +000019import tempfile
John Budorick0f7b2002018-01-19 15:46:17 -080020import threading
zty@chromium.org6279e8a2014-02-13 01:45:25 +000021import traceback
Raul Tambreb946b232019-03-26 14:48:46 +000022
23try:
24 import urlparse
25except ImportError: # For Py3 compatibility
26 import urllib.parse as urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000027
28import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +000029import git_cache
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000030import scm
borenet@google.comb2256212014-05-07 20:57:28 +000031import shutil
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000032import subprocess2
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000033
34
szager@chromium.org71cbb502013-04-19 23:30:15 +000035THIS_FILE_PATH = os.path.abspath(__file__)
36
hinoka@google.com2f2ca142014-01-07 03:59:18 +000037GSUTIL_DEFAULT_PATH = os.path.join(
hinoka@chromium.orgb091aa52014-12-20 01:47:31 +000038 os.path.dirname(os.path.abspath(__file__)), 'gsutil.py')
hinoka@google.com2f2ca142014-01-07 03:59:18 +000039
maruel@chromium.org79d62372015-06-01 18:50:55 +000040
smutae7ea312016-07-18 11:59:41 -070041class NoUsableRevError(gclient_utils.Error):
42 """Raised if requested revision isn't found in checkout."""
43
44
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000045class DiffFiltererWrapper(object):
46 """Simple base class which tracks which file is being diffed and
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000047 replaces instances of its file name in the original and
agable41e3a6c2016-10-20 11:36:56 -070048 working copy lines of the git diff output."""
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000049 index_string = None
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000050 original_prefix = "--- "
51 working_prefix = "+++ "
52
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000053 def __init__(self, relpath, print_func):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000054 # Note that we always use '/' as the path separator to be
agable41e3a6c2016-10-20 11:36:56 -070055 # consistent with cygwin-style output on Windows
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000056 self._relpath = relpath.replace("\\", "/")
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000057 self._current_file = None
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000058 self._print_func = print_func
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000059
maruel@chromium.org6e29d572010-06-04 17:32:20 +000060 def SetCurrentFile(self, current_file):
61 self._current_file = current_file
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000062
iannucci@chromium.org3830a672013-02-19 20:15:14 +000063 @property
64 def _replacement_file(self):
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000065 return posixpath.join(self._relpath, self._current_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000066
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000067 def _Replace(self, line):
68 return line.replace(self._current_file, self._replacement_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000069
70 def Filter(self, line):
71 if (line.startswith(self.index_string)):
72 self.SetCurrentFile(line[len(self.index_string):])
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000073 line = self._Replace(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000074 else:
75 if (line.startswith(self.original_prefix) or
76 line.startswith(self.working_prefix)):
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000077 line = self._Replace(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000078 self._print_func(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000079
80
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000081class GitDiffFilterer(DiffFiltererWrapper):
82 index_string = "diff --git "
83
84 def SetCurrentFile(self, current_file):
85 # Get filename by parsing "a/<filename> b/<filename>"
86 self._current_file = current_file[:(len(current_file)/2)][2:]
87
88 def _Replace(self, line):
89 return re.sub("[a|b]/" + self._current_file, self._replacement_file, line)
90
91
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000092# SCMWrapper base class
93
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000094class SCMWrapper(object):
95 """Add necessary glue between all the supported SCM.
96
msb@chromium.orgd6504212010-01-13 17:34:31 +000097 This is the abstraction layer to bind to different SCM.
98 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000099 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
Edward Lemur231f5ea2018-01-31 19:02:36 +0100100 out_cb=None, print_outbuf=False):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000101 self.url = url
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +0000102 self._root_dir = root_dir
103 if self._root_dir:
104 self._root_dir = self._root_dir.replace('/', os.sep)
105 self.relpath = relpath
106 if self.relpath:
107 self.relpath = self.relpath.replace('/', os.sep)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000108 if self.relpath and self._root_dir:
109 self.checkout_path = os.path.join(self._root_dir, self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000110 if out_fh is None:
111 out_fh = sys.stdout
112 self.out_fh = out_fh
113 self.out_cb = out_cb
Edward Lemur231f5ea2018-01-31 19:02:36 +0100114 self.print_outbuf = print_outbuf
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000115
116 def Print(self, *args, **kwargs):
117 kwargs.setdefault('file', self.out_fh)
118 if kwargs.pop('timestamp', True):
119 self.out_fh.write('[%s] ' % gclient_utils.Elapsed())
120 print(*args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000121
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000122 def RunCommand(self, command, options, args, file_list=None):
agabledebf6c82016-12-21 12:50:12 -0800123 commands = ['update', 'updatesingle', 'revert',
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000124 'revinfo', 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000125
126 if not command in commands:
127 raise gclient_utils.Error('Unknown command %s' % command)
128
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000129 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000130 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000131 command, self.__class__.__name__))
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000132
133 return getattr(self, command)(options, args, file_list)
134
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000135 @staticmethod
136 def _get_first_remote_url(checkout_path):
137 log = scm.GIT.Capture(
138 ['config', '--local', '--get-regexp', r'remote.*.url'],
139 cwd=checkout_path)
140 # Get the second token of the first line of the log.
141 return log.splitlines()[0].split(' ', 1)[1]
142
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000143 def GetCacheMirror(self):
Robert Iannuccia19649b2018-06-29 16:31:45 +0000144 if getattr(self, 'cache_dir', None):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000145 url, _ = gclient_utils.SplitUrlRevision(self.url)
146 return git_cache.Mirror(url)
147 return None
148
smut@google.comd33eab32014-07-07 19:35:18 +0000149 def GetActualRemoteURL(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000150 """Attempt to determine the remote URL for this SCMWrapper."""
smut@google.comd33eab32014-07-07 19:35:18 +0000151 # Git
borenet@google.combda475e2014-03-24 19:04:45 +0000152 if os.path.exists(os.path.join(self.checkout_path, '.git')):
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000153 actual_remote_url = self._get_first_remote_url(self.checkout_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000154
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000155 mirror = self.GetCacheMirror()
156 # If the cache is used, obtain the actual remote URL from there.
157 if (mirror and mirror.exists() and
158 mirror.mirror_path.replace('\\', '/') ==
159 actual_remote_url.replace('\\', '/')):
160 actual_remote_url = self._get_first_remote_url(mirror.mirror_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000161 return actual_remote_url
borenet@google.com88d10082014-03-21 17:24:48 +0000162 return None
163
borenet@google.com4e9be262014-04-08 19:40:30 +0000164 def DoesRemoteURLMatch(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000165 """Determine whether the remote URL of this checkout is the expected URL."""
166 if not os.path.exists(self.checkout_path):
167 # A checkout which doesn't exist can't be broken.
168 return True
169
smut@google.comd33eab32014-07-07 19:35:18 +0000170 actual_remote_url = self.GetActualRemoteURL(options)
borenet@google.com88d10082014-03-21 17:24:48 +0000171 if actual_remote_url:
borenet@google.com8156c9f2014-04-01 16:41:36 +0000172 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
173 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
borenet@google.com88d10082014-03-21 17:24:48 +0000174 else:
175 # This may occur if the self.checkout_path exists but does not contain a
agable41e3a6c2016-10-20 11:36:56 -0700176 # valid git checkout.
borenet@google.com88d10082014-03-21 17:24:48 +0000177 return False
178
borenet@google.comb09097a2014-04-09 19:09:08 +0000179 def _DeleteOrMove(self, force):
180 """Delete the checkout directory or move it out of the way.
181
182 Args:
183 force: bool; if True, delete the directory. Otherwise, just move it.
184 """
borenet@google.comb2256212014-05-07 20:57:28 +0000185 if force and os.environ.get('CHROME_HEADLESS') == '1':
186 self.Print('_____ Conflicting directory found in %s. Removing.'
187 % self.checkout_path)
188 gclient_utils.AddWarning('Conflicting directory %s deleted.'
189 % self.checkout_path)
190 gclient_utils.rmtree(self.checkout_path)
191 else:
192 bad_scm_dir = os.path.join(self._root_dir, '_bad_scm',
193 os.path.dirname(self.relpath))
194
195 try:
196 os.makedirs(bad_scm_dir)
197 except OSError as e:
198 if e.errno != errno.EEXIST:
199 raise
200
201 dest_path = tempfile.mkdtemp(
202 prefix=os.path.basename(self.relpath),
203 dir=bad_scm_dir)
204 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
205 % (self.checkout_path, dest_path))
206 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
207 % (self.checkout_path, dest_path))
208 shutil.move(self.checkout_path, dest_path)
borenet@google.comb09097a2014-04-09 19:09:08 +0000209
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000210
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000211class GitWrapper(SCMWrapper):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000212 """Wrapper for Git"""
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000213 name = 'git'
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000214 remote = 'origin'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000215
Robert Iannuccia19649b2018-06-29 16:31:45 +0000216 @property
217 def cache_dir(self):
218 try:
219 return git_cache.Mirror.GetCachePath()
220 except RuntimeError:
221 return None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000222
John Budorick0f7b2002018-01-19 15:46:17 -0800223 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000224 """Removes 'git+' fake prefix from git URL."""
Henrique Ferreiroe72279d2019-04-17 12:01:50 +0000225 if url and (url.startswith('git+http://') or
226 url.startswith('git+https://')):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000227 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800228 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000229 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
230 if self.out_cb:
231 filter_kwargs['predicate'] = self.out_cb
232 self.filter = gclient_utils.GitFilter(**filter_kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +0000233 self._running_under_rosetta = None
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000234
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000235 def GetCheckoutRoot(self):
236 return scm.GIT.GetCheckoutRoot(self.checkout_path)
237
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000238 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000239 """Returns the given revision's date in ISO-8601 format (which contains the
240 time zone)."""
241 # TODO(floitsch): get the time-stamp of the given revision and not just the
242 # time-stamp of the currently checked out revision.
243 return self._Capture(['log', '-n', '1', '--format=%ai'])
244
Aaron Gablef4068aa2017-12-12 15:14:09 -0800245 def _GetDiffFilenames(self, base):
246 """Returns the names of files modified since base."""
247 return self._Capture(
Raul Tambrecd862e32019-05-10 21:19:00 +0000248 # Filter to remove base if it is None.
249 list(filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only',
250 base])
251 )).split()
Aaron Gablef4068aa2017-12-12 15:14:09 -0800252
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000253 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 15:45:56 -0800254 _, revision = gclient_utils.SplitUrlRevision(self.url)
255 if not revision:
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000256 revision = 'refs/remotes/%s/main' % self.remote
Aaron Gable1853f662018-02-12 15:45:56 -0800257 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000258
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000259 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000260 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000261 repository.
262
263 The patch file is generated from a diff of the merge base of HEAD and
264 its upstream branch.
265 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700266 try:
267 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
268 except subprocess2.CalledProcessError:
269 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000270 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700271 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000272 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000273 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000274
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800275 def _Scrub(self, target, options):
276 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000277 quiet = []
278 if not options.verbose:
279 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800280 self._Run(['reset', '--hard', target] + quiet, options)
281 if options.force and options.delete_unversioned_trees:
282 # where `target` is a commit that contains both upper and lower case
283 # versions of the same file on a case insensitive filesystem, we are
284 # actually in a broken state here. The index will have both 'a' and 'A',
285 # but only one of them will exist on the disk. To progress, we delete
286 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800287 output = self._Capture([
288 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800289 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800290 # --porcelain (v1) looks like:
291 # XY filename
292 try:
293 filename = line[3:]
294 self.Print('_____ Deleting residual after reset: %r.' % filename)
295 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800296 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800297 except OSError:
298 pass
299
John Budorick882c91e2018-07-12 22:11:41 +0000300 def _FetchAndReset(self, revision, file_list, options):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800301 """Equivalent to git fetch; git reset."""
Edward Lemur579c9862018-07-13 23:17:51 +0000302 self._SetFetchConfig(options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000303
dnj@chromium.org680f2172014-06-25 00:39:32 +0000304 self._Fetch(options, prune=True, quiet=options.verbose)
John Budorick882c91e2018-07-12 22:11:41 +0000305 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000306 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800307 files = self._Capture(
308 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambreb946b232019-03-26 14:48:46 +0000309 file_list.extend(
Edward Lemur26a8b9f2019-08-15 20:46:44 +0000310 [os.path.join(self.checkout_path, f) for f in files])
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000311
szager@chromium.org8a139702014-06-20 15:55:01 +0000312 def _DisableHooks(self):
313 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
314 if not os.path.isdir(hook_dir):
315 return
316 for f in os.listdir(hook_dir):
317 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000318 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
319 if os.path.exists(disabled_hook_path):
320 os.remove(disabled_hook_path)
321 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000322
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000323 def _maybe_break_locks(self, options):
324 """This removes all .lock files from this repo's .git directory, if the
325 user passed the --break_repo_locks command line flag.
326
327 In particular, this will cleanup index.lock files, as well as ref lock
328 files.
329 """
330 if options.break_repo_locks:
331 git_dir = os.path.join(self.checkout_path, '.git')
332 for path, _, filenames in os.walk(git_dir):
333 for filename in filenames:
334 if filename.endswith('.lock'):
335 to_break = os.path.join(path, filename)
336 self.Print('breaking lock: %s' % (to_break,))
337 try:
338 os.remove(to_break)
339 except OSError as ex:
340 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
341 raise
342
Edward Lemur3acbc742019-05-30 17:57:35 +0000343 def apply_patch_ref(self, patch_repo, patch_rev, target_rev, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000344 file_list):
345 """Apply a patch on top of the revision we're synced at.
346
Edward Lemur3acbc742019-05-30 17:57:35 +0000347 The patch ref is given by |patch_repo|@|patch_rev|.
348 |target_rev| is usually the branch that the |patch_rev| was uploaded against
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000349 (e.g. 'refs/heads/main'), but this is not required.
Edward Lemur3acbc742019-05-30 17:57:35 +0000350
351 We cherry-pick all commits reachable from |patch_rev| on top of the curret
352 HEAD, excluding those reachable from |target_rev|
353 (i.e. git cherry-pick target_rev..patch_rev).
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000354
355 Graphically, it looks like this:
356
Edward Lemur3acbc742019-05-30 17:57:35 +0000357 ... -> o -> [possibly already landed commits] -> target_rev
358 \
359 -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000360
Edward Lemur3acbc742019-05-30 17:57:35 +0000361 The final checkout state is then:
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000362
Edward Lemur3acbc742019-05-30 17:57:35 +0000363 ... -> HEAD -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000364
365 After application, if |options.reset_patch_ref| is specified, we soft reset
Edward Lemur3acbc742019-05-30 17:57:35 +0000366 the cherry-picked changes, keeping them in git index only.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000367
368 Args:
Edward Lemur3acbc742019-05-30 17:57:35 +0000369 patch_repo: The patch origin.
370 e.g. 'https://foo.googlesource.com/bar'
371 patch_rev: The revision to patch.
372 e.g. 'refs/changes/1234/34/1'.
373 target_rev: The revision to use when finding the merge base.
374 Typically, the branch that the patch was uploaded against.
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000375 e.g. 'refs/heads/main' or 'refs/heads/infra/config'.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000376 options: The options passed to gclient.
377 file_list: A list where modified files will be appended.
378 """
379
Edward Lemurca7d8812018-07-24 17:42:45 +0000380 # Abort any cherry-picks in progress.
381 try:
382 self._Capture(['cherry-pick', '--abort'])
383 except subprocess2.CalledProcessError:
384 pass
385
Edward Lesmesc621b212018-03-21 20:26:56 -0400386 base_rev = self._Capture(['rev-parse', 'HEAD'])
Edward Lemura0ffbe42019-05-01 16:52:18 +0000387
Edward Lemur3acbc742019-05-30 17:57:35 +0000388 if not target_rev:
Edward Lemur4c5c8ab2019-06-07 15:58:13 +0000389 raise gclient_utils.Error('A target revision for the patch must be given')
Edward Lesmesf627d9f2020-07-23 19:50:50 +0000390 elif target_rev.startswith(('refs/heads/', 'refs/branch-heads')):
391 # If |target_rev| is in refs/heads/** or refs/branch-heads/**, try first
392 # to find the corresponding remote ref for it, since |target_rev| might
393 # point to a local ref which is not up to date with the corresponding
394 # remote ref.
Edward Lemur3acbc742019-05-30 17:57:35 +0000395 remote_ref = ''.join(scm.GIT.RefToRemoteRef(target_rev, self.remote))
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000396 self.Print('Trying the corresponding remote ref for %r: %r\n' % (
Edward Lemur3acbc742019-05-30 17:57:35 +0000397 target_rev, remote_ref))
398 if scm.GIT.IsValidRevision(self.checkout_path, remote_ref):
Josip Sokcevic3d7cbce2021-10-05 20:48:04 +0000399 # refs/remotes may need to be updated to cleanly cherry-pick changes.
400 # See https://crbug.com/1255178.
401 self._Capture(['fetch', '--no-tags', self.remote, target_rev])
Edward Lemur3acbc742019-05-30 17:57:35 +0000402 target_rev = remote_ref
403 elif not scm.GIT.IsValidRevision(self.checkout_path, target_rev):
404 # Fetch |target_rev| if it's not already available.
405 url, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmes07a68342021-04-20 23:39:30 +0000406 mirror = self._GetMirror(url, options, target_rev, target_rev)
Edward Lemur3acbc742019-05-30 17:57:35 +0000407 if mirror:
408 rev_type = 'branch' if target_rev.startswith('refs/') else 'hash'
409 self._UpdateMirrorIfNotContains(mirror, options, rev_type, target_rev)
410 self._Fetch(options, refspec=target_rev)
Edward Lemura0ffbe42019-05-01 16:52:18 +0000411
Edward Lemur3acbc742019-05-30 17:57:35 +0000412 self.Print('===Applying patch===')
413 self.Print('Revision to patch is %r @ %r.' % (patch_repo, patch_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000414 self.Print('Current dir is %r' % self.checkout_path)
Edward Lesmesc621b212018-03-21 20:26:56 -0400415 self._Capture(['reset', '--hard'])
danakjd5c0b562019-11-08 17:27:47 +0000416 self._Capture(['fetch', '--no-tags', patch_repo, patch_rev])
Edward Lemurca7d8812018-07-24 17:42:45 +0000417 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400418
Edward Lemur3acbc742019-05-30 17:57:35 +0000419 if not options.rebase_patch_ref:
420 self._Capture(['checkout', patch_rev])
Robert Iannuccic39a7782019-11-01 18:30:33 +0000421 # Adjust base_rev to be the first parent of our checked out patch ref;
422 # This will allow us to correctly extend `file_list`, and will show the
423 # correct file-list to programs which do `git diff --cached` expecting to
424 # see the patch diff.
425 base_rev = self._Capture(['rev-parse', patch_rev+'~'])
426
Edward Lemur3acbc742019-05-30 17:57:35 +0000427 else:
Robert Iannuccic39a7782019-11-01 18:30:33 +0000428 self.Print('Will cherrypick %r .. %r on top of %r.' % (
429 target_rev, patch_rev, base_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000430 try:
431 if scm.GIT.IsAncestor(self.checkout_path, patch_rev, target_rev):
432 # If |patch_rev| is an ancestor of |target_rev|, check it out.
Edward Lemurca7d8812018-07-24 17:42:45 +0000433 self._Capture(['checkout', patch_rev])
434 else:
435 # If a change was uploaded on top of another change, which has already
436 # landed, one of the commits in the cherry-pick range will be
437 # redundant, since it has already landed and its changes incorporated
438 # in the tree.
439 # We pass '--keep-redundant-commits' to ignore those changes.
Edward Lemur3acbc742019-05-30 17:57:35 +0000440 self._Capture(['cherry-pick', target_rev + '..' + patch_rev,
Edward Lemurca7d8812018-07-24 17:42:45 +0000441 '--keep-redundant-commits'])
442
Edward Lemur3acbc742019-05-30 17:57:35 +0000443 except subprocess2.CalledProcessError as e:
444 self.Print('Failed to apply patch.')
445 self.Print('Revision to patch was %r @ %r.' % (patch_repo, patch_rev))
446 self.Print('Tried to cherrypick %r .. %r on top of %r.' % (
447 target_rev, patch_rev, base_rev))
448 self.Print('Current dir is %r' % self.checkout_path)
449 self.Print('git returned non-zero exit status %s:\n%s' % (
Edward Lemur979fa782019-08-13 22:44:05 +0000450 e.returncode, e.stderr.decode('utf-8')))
Edward Lemur3acbc742019-05-30 17:57:35 +0000451 # Print the current status so that developers know what changes caused
452 # the patch failure, since git cherry-pick doesn't show that
453 # information.
454 self.Print(self._Capture(['status']))
455 try:
456 self._Capture(['cherry-pick', '--abort'])
457 except subprocess2.CalledProcessError:
458 pass
459 raise
Edward Lemurca7d8812018-07-24 17:42:45 +0000460
Edward Lemur3acbc742019-05-30 17:57:35 +0000461 if file_list is not None:
462 file_list.extend(self._GetDiffFilenames(base_rev))
Edward Lemurca7d8812018-07-24 17:42:45 +0000463
Edward Lesmesc621b212018-03-21 20:26:56 -0400464 if options.reset_patch_ref:
465 self._Capture(['reset', '--soft', base_rev])
466
msb@chromium.orge28e4982009-09-25 20:51:45 +0000467 def update(self, options, args, file_list):
468 """Runs git to update or transparently checkout the working copy.
469
470 All updated files will be appended to file_list.
471
472 Raises:
473 Error: if can't get URL for relative path.
474 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000475 if args:
476 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
477
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000478 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000479
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000480 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000481 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000482 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000483 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000484 # Override the revision number.
485 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000486 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000487 # Check again for a revision in case an initial ref was specified
488 # in the url, for example bla.git@refs/heads/custombranch
489 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000490 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000491 if not revision:
Josip Sokcevic091f5ac2021-01-14 23:14:21 +0000492 # If a dependency is not pinned, track the default remote branch.
493 revision = scm.GIT.GetRemoteHeadRef(self.checkout_path, self.url,
494 self.remote)
Edward Lesmes4ea67bb2021-04-20 17:33:52 +0000495 if revision.startswith('origin/'):
496 revision = 'refs/remotes/' + revision
msb@chromium.orge28e4982009-09-25 20:51:45 +0000497
Tomasz Wiszkowskid4e66882021-08-19 21:35:09 +0000498 if managed and platform.system() == 'Windows':
szager@chromium.org8a139702014-06-20 15:55:01 +0000499 self._DisableHooks()
500
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000501 printed_path = False
502 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000503 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700504 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000505 verbose = ['--verbose']
506 printed_path = True
507
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000508 revision_ref = revision
509 if ':' in revision:
510 revision_ref, _, revision = revision.partition(':')
511
Edward Lesmes8073a502020-04-15 02:11:14 +0000512 if revision_ref.startswith('refs/branch-heads'):
513 options.with_branch_heads = True
514
Edward Lesmes07a68342021-04-20 23:39:30 +0000515 mirror = self._GetMirror(url, options, revision, revision_ref)
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000516 if mirror:
517 url = mirror.mirror_path
Edward Lemurdb5c5ad2019-03-07 16:00:24 +0000518
John Budorick882c91e2018-07-12 22:11:41 +0000519 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
520 if remote_ref:
521 # Rewrite remote refs to their local equivalents.
522 revision = ''.join(remote_ref)
523 rev_type = "branch"
524 elif revision.startswith('refs/'):
525 # Local branch? We probably don't want to support, since DEPS should
526 # always specify branches as they are in the upstream repo.
527 rev_type = "branch"
528 else:
529 # hash is also a tag, only make a distinction at checkout
530 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000531
primiano@chromium.org1c127382015-02-17 11:15:40 +0000532 # If we are going to introduce a new project, there is a possibility that
533 # we are syncing back to a state where the project was originally a
534 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
535 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
536 # In such case, we might have a backup of the former .git folder, which can
537 # be used to avoid re-fetching the entire repo again (useful for bisects).
538 backup_dir = self.GetGitBackupDirPath()
539 target_dir = os.path.join(self.checkout_path, '.git')
540 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
541 gclient_utils.safe_makedirs(self.checkout_path)
542 os.rename(backup_dir, target_dir)
543 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800544 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000545
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000546 if (not os.path.exists(self.checkout_path) or
547 (os.path.isdir(self.checkout_path) and
548 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000549 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000550 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000551 try:
John Budorick882c91e2018-07-12 22:11:41 +0000552 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000553 except subprocess2.CalledProcessError:
554 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000555 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000556 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800557 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20 +0000558 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambrec2f74c12019-03-19 05:55:53 +0000559 file_list.extend(
Edward Lemur979fa782019-08-13 22:44:05 +0000560 [os.path.join(self.checkout_path, f) for f in files])
John Budorick21a51b32018-09-19 19:39:20 +0000561 if mirror:
562 self._Capture(
563 ['remote', 'set-url', '--push', 'origin', mirror.url])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000564 if not verbose:
565 # Make the output a little prettier. It's nice to have some whitespace
566 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000567 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000568 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000569
John Budorick21a51b32018-09-19 19:39:20 +0000570 if mirror:
571 self._Capture(
572 ['remote', 'set-url', '--push', 'origin', mirror.url])
573
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000574 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000575 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000576 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
577 return self._Capture(['rev-parse', '--verify', 'HEAD'])
578
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000579 self._maybe_break_locks(options)
580
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000581 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000582 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000583
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000584 # See if the url has changed (the unittests use git://foo for the url, let
585 # that through).
586 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
587 return_early = False
588 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
589 # unit test pass. (and update the comment above)
590 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
591 # This allows devs to use experimental repos which have a different url
592 # but whose branch(s) are the same as official repos.
Raul Tambrecd862e32019-05-10 21:19:00 +0000593 if (current_url.rstrip('/') != url.rstrip('/') and url != 'git://foo' and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000594 subprocess2.capture(
Aaron Gableac9b0f32019-04-18 17:38:37 +0000595 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000596 cwd=self.checkout_path).strip() != 'False'):
Anthony Polito486f1812020-08-04 23:40:33 +0000597 self.Print('_____ switching %s from %s to new upstream %s' % (
598 self.relpath, current_url, url))
iannucci@chromium.org78514212014-08-20 23:08:00 +0000599 if not (options.force or options.reset):
600 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700601 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000602 # Switch over to the new upstream
603 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000604 if mirror:
605 with open(os.path.join(
606 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
607 'w') as fh:
608 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000609 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
610 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000611
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000612 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000613 else:
John Budorick882c91e2018-07-12 22:11:41 +0000614 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000615
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000616 if return_early:
617 return self._Capture(['rev-parse', '--verify', 'HEAD'])
618
msb@chromium.org5bde4852009-12-14 16:47:12 +0000619 cur_branch = self._GetCurrentBranch()
620
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000621 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000622 # 0) HEAD is detached. Probably from our initial clone.
623 # - make sure HEAD is contained by a named ref, then update.
624 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700625 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000626 # - try to rebase onto the new hash or branch
627 # 2) current branch is tracking a remote branch with local committed
628 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000629 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000630 # 3) current branch is tracking a remote branch w/or w/out changes, and
631 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000632 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000633 # 4) current branch is tracking a remote branch, but DEPS switches to a
634 # different remote branch, and
635 # a) current branch has no local changes, and --force:
636 # - checkout new branch
637 # b) current branch has local changes, and --force and --reset:
638 # - checkout new branch
639 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000640
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000641 # GetUpstreamBranch returns something like 'refs/remotes/origin/main' for
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000642 # a tracking branch
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000643 # or 'main' if not a tracking branch (it's based on a specific rev/hash)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000644 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000645 if cur_branch is None:
646 upstream_branch = None
647 current_type = "detached"
648 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000649 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000650 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
651 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
652 current_type = "hash"
653 logging.debug("Current branch is not tracking an upstream (remote)"
654 " branch.")
655 elif upstream_branch.startswith('refs/remotes'):
656 current_type = "branch"
657 else:
658 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000659
Edward Lemur579c9862018-07-13 23:17:51 +0000660 self._SetFetchConfig(options)
Edward Lemur579c9862018-07-13 23:17:51 +0000661
Michael Spang73fac912019-03-08 18:44:19 +0000662 # Fetch upstream if we don't already have |revision|.
John Budorick882c91e2018-07-12 22:11:41 +0000663 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
Michael Spang73fac912019-03-08 18:44:19 +0000664 self._Fetch(options, prune=options.force)
665
666 if not scm.GIT.IsValidRevision(self.checkout_path, revision,
667 sha_only=True):
668 # Update the remotes first so we have all the refs.
669 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
670 cwd=self.checkout_path)
671 if verbose:
672 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000673
John Budorick882c91e2018-07-12 22:11:41 +0000674 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200675
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000676 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000677 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000678 target = 'HEAD'
679 if options.upstream and upstream_branch:
680 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800681 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000682
msb@chromium.org786fb682010-06-02 15:16:23 +0000683 if current_type == 'detached':
684 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800685 # We just did a Scrub, this is as clean as it's going to get. In
686 # particular if HEAD is a commit that contains two versions of the same
687 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
688 # to actually "Clean" the checkout; that commit is uncheckoutable on this
689 # system. The best we can do is carry forward to the checkout step.
690 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000691 self._CheckClean(revision)
692 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000693 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000694 self.Print('Up-to-date; skipping checkout.')
695 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000696 # 'git checkout' may need to overwrite existing untracked files. Allow
697 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000698 self._Checkout(
699 options,
John Budorick882c91e2018-07-12 22:11:41 +0000700 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000701 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000702 quiet=True,
703 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000704 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000705 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000706 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000707 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700708 # Can't find a merge-base since we don't know our upstream. That makes
709 # this command VERY likely to produce a rebase failure. For now we
710 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000711 upstream_branch = self.remote
712 if options.revision or deps_revision:
713 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700714 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700715 printed_path=printed_path, merge=options.merge)
716 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000717 elif rev_type == 'hash':
718 # case 2
719 self._AttemptRebase(upstream_branch, file_list, options,
720 newbase=revision, printed_path=printed_path,
721 merge=options.merge)
722 printed_path = True
723 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000724 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000725 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000726 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000727 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000728 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000729 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000730 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000731 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
732 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000733 force_switch = False
734 if options.force:
735 try:
John Budorick882c91e2018-07-12 22:11:41 +0000736 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000737 # case 4a
738 force_switch = True
739 except gclient_utils.Error as e:
740 if options.reset:
741 # case 4b
742 force_switch = True
743 else:
744 switch_error = '%s\n%s' % (e.message, switch_error)
745 if force_switch:
746 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000747 (upstream_branch, new_base))
748 switch_branch = 'gclient_' + remote_ref[1]
749 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000750 self._Checkout(options, switch_branch, force=True, quiet=True)
751 else:
752 # case 4c
753 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000754 else:
755 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800756 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000757 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000758 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000759 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000760 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000761 if options.merge:
762 merge_args.append('--ff')
763 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000764 merge_args.append('--ff-only')
765 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000766 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000767 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700768 rebase_files = []
Edward Lemur979fa782019-08-13 22:44:05 +0000769 if re.match(b'fatal: Not possible to fast-forward, aborting.',
770 e.stderr):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000771 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000772 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700773 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000774 printed_path = True
775 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000776 if not options.auto_rebase:
777 try:
778 action = self._AskForData(
779 'Cannot %s, attempt to rebase? '
780 '(y)es / (q)uit / (s)kip : ' %
781 ('merge' if options.merge else 'fast-forward merge'),
782 options)
783 except ValueError:
784 raise gclient_utils.Error('Invalid Character')
785 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700786 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000787 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000788 printed_path = True
789 break
790 elif re.match(r'quit|q', action, re.I):
791 raise gclient_utils.Error("Can't fast-forward, please merge or "
792 "rebase manually.\n"
793 "cd %s && git " % self.checkout_path
794 + "rebase %s" % upstream_branch)
795 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000796 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000797 return
798 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000799 self.Print('Input not recognized')
Edward Lemur979fa782019-08-13 22:44:05 +0000800 elif re.match(b"error: Your local changes to '.*' would be "
801 b"overwritten by merge. Aborting.\nPlease, commit your "
802 b"changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000803 e.stderr):
804 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000805 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700806 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000807 printed_path = True
Edward Lemur979fa782019-08-13 22:44:05 +0000808 raise gclient_utils.Error(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000809 else:
810 # Some other problem happened with the merge
811 logging.error("Error during fast-forward merge in %s!" % self.relpath)
Edward Lemur979fa782019-08-13 22:44:05 +0000812 self.Print(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000813 raise
814 else:
815 # Fast-forward merge was successful
816 if not re.match('Already up-to-date.', merge_output) or verbose:
817 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700818 self.Print('_____ %s at %s' % (self.relpath, revision),
819 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000820 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000821 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000822 if not verbose:
823 # Make the output a little prettier. It's nice to have some
824 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000825 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000826
agablec3937b92016-10-25 10:13:03 -0700827 if file_list is not None:
828 file_list.extend(
829 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000830
831 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000832 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700833 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000834 '\nConflict while rebasing this branch.\n'
835 'Fix the conflict and run gclient again.\n'
836 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700837 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000838
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000839 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000840 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
841 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000842
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000843 # If --reset and --delete_unversioned_trees are specified, remove any
844 # untracked directories.
845 if options.reset and options.delete_unversioned_trees:
846 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
847 # merge-base by default), so doesn't include untracked files. So we use
848 # 'git ls-files --directory --others --exclude-standard' here directly.
849 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800850 ['-c', 'core.quotePath=false', 'ls-files',
851 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000852 self.checkout_path)
853 for path in (p for p in paths.splitlines() if p.endswith('/')):
854 full_path = os.path.join(self.checkout_path, path)
855 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000856 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000857 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000858
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000859 return self._Capture(['rev-parse', '--verify', 'HEAD'])
860
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000861 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000862 """Reverts local modifications.
863
864 All reverted files will be appended to file_list.
865 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000866 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000867 # revert won't work if the directory doesn't exist. It needs to
868 # checkout instead.
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000869 self.Print('_____ %s is missing, syncing instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000870 # Don't reuse the args.
871 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000872
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000873 default_rev = "refs/heads/main"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000874 if options.upstream:
875 if self._GetCurrentBranch():
876 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
877 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000878 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000879 if not deps_revision:
880 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000881 if deps_revision.startswith('refs/heads/'):
882 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700883 try:
884 deps_revision = self.GetUsableRev(deps_revision, options)
885 except NoUsableRevError as e:
886 # If the DEPS entry's url and hash changed, try to update the origin.
887 # See also http://crbug.com/520067.
John Budorickd94f8ea2020-03-27 15:55:24 +0000888 logging.warning(
889 "Couldn't find usable revision, will retrying to update instead: %s",
smutae7ea312016-07-18 11:59:41 -0700890 e.message)
891 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000892
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000893 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800894 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000895
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800896 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000897 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000898
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000899 if file_list is not None:
900 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
901
902 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000903 """Returns revision"""
904 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000905
msb@chromium.orge28e4982009-09-25 20:51:45 +0000906 def runhooks(self, options, args, file_list):
907 self.status(options, args, file_list)
908
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000909 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000910 """Display status information."""
911 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000912 self.Print('________ couldn\'t run status in %s:\n'
913 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000914 else:
Anthony Politobb457342019-11-15 22:26:01 +0000915 merge_base = []
916 if self.url:
917 _, base_rev = gclient_utils.SplitUrlRevision(self.url)
918 if base_rev:
919 merge_base = [base_rev]
Aaron Gablef4068aa2017-12-12 15:14:09 -0800920 self._Run(
921 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
Edward Lemur24146be2019-08-01 21:44:52 +0000922 options, always_show_header=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000923 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800924 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000925 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000926
smutae7ea312016-07-18 11:59:41 -0700927 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700928 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700929 sha1 = None
930 if not os.path.isdir(self.checkout_path):
931 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800932 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700933
934 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
935 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700936 else:
agable41e3a6c2016-10-20 11:36:56 -0700937 # May exist in origin, but we don't have it yet, so fetch and look
938 # again.
939 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700940 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
941 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700942
943 if not sha1:
944 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800945 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700946
947 return sha1
948
primiano@chromium.org1c127382015-02-17 11:15:40 +0000949 def GetGitBackupDirPath(self):
950 """Returns the path where the .git folder for the current project can be
951 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
952 return os.path.join(self._root_dir,
953 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
954
Edward Lesmes07a68342021-04-20 23:39:30 +0000955 def _GetMirror(self, url, options, revision=None, revision_ref=None):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000956 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000957 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000958 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000959 mirror_kwargs = {
960 'print_func': self.filter,
Edward Lesmes07a68342021-04-20 23:39:30 +0000961 'refs': [],
962 'commits': [],
hinoka@google.comb1b54572014-04-16 22:29:23 +0000963 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000964 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
965 mirror_kwargs['refs'].append('refs/branch-heads/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000966 elif revision_ref and revision_ref.startswith('refs/branch-heads/'):
967 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000968 if hasattr(options, 'with_tags') and options.with_tags:
969 mirror_kwargs['refs'].append('refs/tags/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000970 elif revision_ref and revision_ref.startswith('refs/tags/'):
971 mirror_kwargs['refs'].append(revision_ref)
Edward Lesmes07a68342021-04-20 23:39:30 +0000972 if revision and not revision.startswith('refs/'):
973 mirror_kwargs['commits'].append(revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000974 return git_cache.Mirror(url, **mirror_kwargs)
975
John Budorick882c91e2018-07-12 22:11:41 +0000976 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800977 """Update a git mirror by fetching the latest commits from the remote,
978 unless mirror already contains revision whose type is sha1 hash.
979 """
John Budorick882c91e2018-07-12 22:11:41 +0000980 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800981 if options.verbose:
982 self.Print('skipping mirror update, it has rev=%s already' % revision,
983 timestamp=False)
984 return
985
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000986 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000987 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000988 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000989 depth = 10
990 else:
991 depth = 10000
992 else:
993 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000994 mirror.populate(verbose=options.verbose,
995 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000996 depth=depth,
Vadim Shtayura08049e22017-10-11 00:14:52 +0000997 lock_timeout=getattr(options, 'lock_timeout', 0))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000998
John Budorick882c91e2018-07-12 22:11:41 +0000999 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001000 """Clone a git repository from the given URL.
1001
msb@chromium.org786fb682010-06-02 15:16:23 +00001002 Once we've cloned the repo, we checkout a working branch if the specified
1003 revision is a branch head. If it is a tag or a specific commit, then we
1004 leave HEAD detached as it makes future updates simpler -- in this case the
1005 user should first create a new branch or switch to an existing branch before
1006 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001007 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001008 # git clone doesn't seem to insert a newline properly before printing
1009 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001010 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +00001011 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +00001012 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001013 if self.cache_dir:
1014 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001015 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001016 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001017 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +00001018 # If the parent directory does not exist, Git clone on Windows will not
1019 # create it, so we need to do it manually.
1020 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001021 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001022
1023 template_dir = None
1024 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +00001025 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001026 # In the case of a subproject, the pinned sha is not necessarily the
1027 # head of the remote branch (so we can't just use --depth=N). Instead,
1028 # we tell git to fetch all the remote objects from SHA..HEAD by means of
1029 # a template git dir which has a 'shallow' file pointing to the sha.
1030 template_dir = tempfile.mkdtemp(
1031 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
1032 dir=parent_dir)
1033 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
1034 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
1035 template_file.write(revision)
1036 clone_cmd.append('--template=' + template_dir)
1037 else:
1038 # Otherwise, we're just interested in the HEAD. Just use --depth.
1039 clone_cmd.append('--depth=1')
1040
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001041 tmp_dir = tempfile.mkdtemp(
1042 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
1043 dir=parent_dir)
1044 try:
1045 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001046 if self.print_outbuf:
1047 print_stdout = True
Edward Lemur24146be2019-08-01 21:44:52 +00001048 filter_fn = None
Edward Lemur231f5ea2018-01-31 19:02:36 +01001049 else:
1050 print_stdout = False
Edward Lemur24146be2019-08-01 21:44:52 +00001051 filter_fn = self.filter
Edward Lemur231f5ea2018-01-31 19:02:36 +01001052 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
Edward Lemur24146be2019-08-01 21:44:52 +00001053 print_stdout=print_stdout, filter_fn=filter_fn)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001054 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +00001055 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
1056 os.path.join(self.checkout_path, '.git'))
Edward Lesmesd4e20f22020-07-15 21:11:08 +00001057 # TODO(https://github.com/git-for-windows/git/issues/2569): Remove once
1058 # fixed.
1059 if sys.platform.startswith('win'):
1060 try:
1061 self._Run(['config', '--unset', 'core.worktree'], options,
1062 cwd=self.checkout_path)
1063 except subprocess2.CalledProcessError:
1064 pass
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001065 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001066 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001067 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001068 finally:
1069 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001070 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001071 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001072 if template_dir:
1073 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +00001074 self._SetFetchConfig(options)
1075 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001076 revision = self._AutoFetchRef(options, revision)
1077 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1078 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +00001079 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +00001080 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001081 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +00001082 ('Checked out %s to a detached HEAD. Before making any commits\n'
1083 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1084 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +00001085 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001086
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001087 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001088 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001089 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001090 raise gclient_utils.Error("Background task requires input. Rerun "
1091 "gclient with --jobs=1 so that\n"
1092 "interaction is possible.")
Edward Lesmesae3586b2020-03-23 21:21:14 +00001093 return gclient_utils.AskForData(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001094
1095
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001096 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001097 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001098 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001099 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001100 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001101 revision = upstream
1102 if newbase:
1103 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001104 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001105 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001106 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001107 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001108 printed_path = True
1109 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001110 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001111
1112 if merge:
1113 merge_output = self._Capture(['merge', revision])
1114 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001115 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001116 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001117
1118 # Build the rebase command here using the args
1119 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1120 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001121 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001122 rebase_cmd.append('--verbose')
1123 if newbase:
1124 rebase_cmd.extend(['--onto', newbase])
1125 rebase_cmd.append(upstream)
1126 if branch:
1127 rebase_cmd.append(branch)
1128
1129 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001130 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
Raul Tambreb946b232019-03-26 14:48:46 +00001131 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001132 if (re.match(br'cannot rebase: you have unstaged changes', e.stderr) or
1133 re.match(br'cannot rebase: your index contains uncommitted changes',
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001134 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001135 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001136 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001137 'Cannot rebase because of unstaged changes.\n'
1138 '\'git reset --hard HEAD\' ?\n'
1139 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001140 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001141 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001142 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001143 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001144 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001145 break
1146 elif re.match(r'quit|q', rebase_action, re.I):
1147 raise gclient_utils.Error("Please merge or rebase manually\n"
1148 "cd %s && git " % self.checkout_path
1149 + "%s" % ' '.join(rebase_cmd))
1150 elif re.match(r'show|s', rebase_action, re.I):
Edward Lemur979fa782019-08-13 22:44:05 +00001151 self.Print('%s' % e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001152 continue
1153 else:
1154 gclient_utils.Error("Input not recognized")
1155 continue
Edward Lemur979fa782019-08-13 22:44:05 +00001156 elif re.search(br'^CONFLICT', e.stdout, re.M):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001157 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1158 "Fix the conflict and run gclient again.\n"
1159 "See 'man git-rebase' for details.\n")
1160 else:
Edward Lemur979fa782019-08-13 22:44:05 +00001161 self.Print(e.stdout.decode('utf-8').strip())
1162 self.Print('Rebase produced error output:\n%s' %
1163 e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001164 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1165 "manually.\ncd %s && git " %
1166 self.checkout_path
1167 + "%s" % ' '.join(rebase_cmd))
1168
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001169 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001170 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001171 # Make the output a little prettier. It's nice to have some
1172 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001173 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001174
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001175 @staticmethod
1176 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001177 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1178 if not ok:
1179 raise gclient_utils.Error('git version %s < minimum required %s' %
1180 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001181
John Budorick882c91e2018-07-12 22:11:41 +00001182 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001183 # Special case handling if all 3 conditions are met:
1184 # * the mirros have recently changed, but deps destination remains same,
1185 # * the git histories of mirrors are conflicting.
1186 # * git cache is used
1187 # This manifests itself in current checkout having invalid HEAD commit on
1188 # most git operations. Since git cache is used, just deleted the .git
1189 # folder, and re-create it by cloning.
1190 try:
1191 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1192 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001193 if (b'fatal: bad object HEAD' in e.stderr
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001194 and self.cache_dir and self.cache_dir in url):
1195 self.Print((
1196 'Likely due to DEPS change with git cache_dir, '
1197 'the current commit points to no longer existing object.\n'
1198 '%s' % e)
1199 )
1200 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001201 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001202 else:
1203 raise
1204
msb@chromium.org786fb682010-06-02 15:16:23 +00001205 def _IsRebasing(self):
1206 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1207 # have a plumbing command to determine whether a rebase is in progress, so
1208 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1209 g = os.path.join(self.checkout_path, '.git')
1210 return (
1211 os.path.isdir(os.path.join(g, "rebase-merge")) or
1212 os.path.isdir(os.path.join(g, "rebase-apply")))
1213
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001214 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001215 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1216 if os.path.exists(lockfile):
1217 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001218 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001219 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1220 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001221 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001222
msb@chromium.org786fb682010-06-02 15:16:23 +00001223 # Make sure the tree is clean; see git-rebase.sh for reference
1224 try:
1225 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001226 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001227 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001228 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001229 '\tYou have unstaged changes.\n'
1230 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001231 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001232 try:
1233 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001234 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001235 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001236 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001237 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001238 '\tYour index contains uncommitted changes\n'
1239 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001240 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001241
agable83faed02016-10-24 14:37:10 -07001242 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001243 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1244 # reference by a commit). If not, error out -- most likely a rebase is
1245 # in progress, try to detect so we can give a better error.
1246 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001247 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1248 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001249 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001250 # Commit is not contained by any rev. See if the user is rebasing:
1251 if self._IsRebasing():
1252 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001253 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001254 '\tAlready in a conflict, i.e. (no branch).\n'
1255 '\tFix the conflict and run gclient again.\n'
1256 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1257 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001258 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001259 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001260 name = ('saved-by-gclient-' +
1261 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001262 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001263 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001264 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001265
msb@chromium.org5bde4852009-12-14 16:47:12 +00001266 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001267 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001268 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001269 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001270 return None
1271 return branch
1272
borenet@google.comc3e09d22014-04-10 13:58:18 +00001273 def _Capture(self, args, **kwargs):
Mike Frysinger286fb162019-09-30 03:14:10 +00001274 set_git_dir = 'cwd' not in kwargs
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001275 kwargs.setdefault('cwd', self.checkout_path)
1276 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001277 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001278 env = scm.GIT.ApplyEnvVars(kwargs)
Mike Frysinger286fb162019-09-30 03:14:10 +00001279 # If an explicit cwd isn't set, then default to the .git/ subdir so we get
1280 # stricter behavior. This can be useful in cases of slight corruption --
1281 # we don't accidentally go corrupting parent git checks too. See
1282 # https://crbug.com/1000825 for an example.
1283 if set_git_dir:
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001284 git_dir = os.path.abspath(os.path.join(self.checkout_path, '.git'))
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001285 # Depending on how the .gclient file was defined, self.checkout_path
1286 # might be set to a unicode string, not a regular string; on Windows
1287 # Python2, we can't set env vars to be unicode strings, so we
1288 # forcibly cast the value to a string before setting it.
1289 env.setdefault('GIT_DIR', str(git_dir))
Raul Tambrecd862e32019-05-10 21:19:00 +00001290 ret = subprocess2.check_output(
1291 ['git'] + args, env=env, **kwargs).decode('utf-8')
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001292 if strip:
1293 ret = ret.strip()
Erik Chene16ffff2019-10-14 20:35:53 +00001294 self.Print('Finished running: %s %s' % ('git', ' '.join(args)))
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001295 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001296
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001297 def _Checkout(self, options, ref, force=False, quiet=None):
1298 """Performs a 'git-checkout' operation.
1299
1300 Args:
1301 options: The configured option set
1302 ref: (str) The branch/commit to checkout
Quinten Yearsley925cedb2020-04-13 17:49:39 +00001303 quiet: (bool/None) Whether or not the checkout should pass '--quiet'; if
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001304 'None', the behavior is inferred from 'options.verbose'.
1305 Returns: (str) The output of the checkout operation
1306 """
1307 if quiet is None:
1308 quiet = (not options.verbose)
1309 checkout_args = ['checkout']
1310 if force:
1311 checkout_args.append('--force')
1312 if quiet:
1313 checkout_args.append('--quiet')
1314 checkout_args.append(ref)
1315 return self._Capture(checkout_args)
1316
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001317 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1318 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001319 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemur9a5e3bd2019-04-02 23:37:45 +00001320 # When updating, the ref is modified to be a remote ref .
1321 # (e.g. refs/heads/NAME becomes refs/remotes/REMOTE/NAME).
1322 # Try to reverse that mapping.
1323 original_ref = scm.GIT.RemoteRefToRef(refspec, self.remote)
1324 if original_ref:
1325 refspec = original_ref + ':' + refspec
1326 # When a mirror is configured, it only fetches
1327 # refs/{heads,branch-heads,tags}/*.
1328 # If asked to fetch other refs, we must fetch those directly from the
1329 # repository, and not from the mirror.
1330 if not original_ref.startswith(
1331 ('refs/heads/', 'refs/branch-heads/', 'refs/tags/')):
1332 remote, _ = gclient_utils.SplitUrlRevision(self.url)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001333 fetch_cmd = cfg + [
1334 'fetch',
1335 remote or self.remote,
1336 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001337 if refspec:
1338 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001339
1340 if prune:
1341 fetch_cmd.append('--prune')
1342 if options.verbose:
1343 fetch_cmd.append('--verbose')
danakjd5c0b562019-11-08 17:27:47 +00001344 if not hasattr(options, 'with_tags') or not options.with_tags:
1345 fetch_cmd.append('--no-tags')
dnj@chromium.org680f2172014-06-25 00:39:32 +00001346 elif quiet:
1347 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001348 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001349
Edward Lemur579c9862018-07-13 23:17:51 +00001350 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001351 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1352 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001353 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001354 try:
1355 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1356 options)
1357 self._Run(['config', 'remote.%s.fetch' % self.remote,
1358 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1359 except subprocess2.CalledProcessError as e:
1360 # If exit code was 5, it means we attempted to unset a config that
1361 # didn't exist. Ignore it.
1362 if e.returncode != 5:
1363 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001364 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001365 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001366 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1367 '^\\+refs/branch-heads/\\*:.*$']
1368 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001369 if hasattr(options, 'with_tags') and options.with_tags:
1370 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1371 '+refs/tags/*:refs/tags/*',
1372 '^\\+refs/tags/\\*:.*$']
1373 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001374
John Budorick882c91e2018-07-12 22:11:41 +00001375 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001376 """Attempts to fetch |revision| if not available in local repo.
1377
1378 Returns possibly updated revision."""
Edward Lemure0ba7b82020-03-11 20:31:32 +00001379 if not scm.GIT.IsValidRevision(self.checkout_path, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001380 self._Fetch(options, refspec=revision)
1381 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1382 return revision
1383
Edward Lemur24146be2019-08-01 21:44:52 +00001384 def _Run(self, args, options, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001385 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001386 kwargs.setdefault('cwd', self.checkout_path)
Edward Lemur24146be2019-08-01 21:44:52 +00001387 kwargs.setdefault('filter_fn', self.filter)
1388 kwargs.setdefault('show_header', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001389 env = scm.GIT.ApplyEnvVars(kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +00001390
agable@chromium.org772efaf2014-04-01 02:35:44 +00001391 cmd = ['git'] + args
Edward Lemur24146be2019-08-01 21:44:52 +00001392 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001393
1394
1395class CipdPackage(object):
1396 """A representation of a single CIPD package."""
1397
John Budorickd3ba72b2018-03-20 12:27:42 -07001398 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001399 self._authority_for_subdir = authority_for_subdir
1400 self._name = name
1401 self._version = version
1402
1403 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001404 def authority_for_subdir(self):
1405 """Whether this package has authority to act on behalf of its subdir.
1406
1407 Some operations should only be performed once per subdirectory. A package
1408 that has authority for its subdirectory is the only package that should
1409 perform such operations.
1410
1411 Returns:
1412 bool; whether this package has subdir authority.
1413 """
1414 return self._authority_for_subdir
1415
1416 @property
1417 def name(self):
1418 return self._name
1419
1420 @property
1421 def version(self):
1422 return self._version
1423
1424
1425class CipdRoot(object):
1426 """A representation of a single CIPD root."""
1427 def __init__(self, root_dir, service_url):
1428 self._all_packages = set()
1429 self._mutator_lock = threading.Lock()
1430 self._packages_by_subdir = collections.defaultdict(list)
1431 self._root_dir = root_dir
1432 self._service_url = service_url
1433
1434 def add_package(self, subdir, package, version):
1435 """Adds a package to this CIPD root.
1436
1437 As far as clients are concerned, this grants both root and subdir authority
1438 to packages arbitrarily. (The implementation grants root authority to the
1439 first package added and subdir authority to the first package added for that
1440 subdir, but clients should not depend on or expect that behavior.)
1441
1442 Args:
1443 subdir: str; relative path to where the package should be installed from
1444 the cipd root directory.
1445 package: str; the cipd package name.
1446 version: str; the cipd package version.
1447 Returns:
1448 CipdPackage; the package that was created and added to this root.
1449 """
1450 with self._mutator_lock:
1451 cipd_package = CipdPackage(
1452 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001453 not self._packages_by_subdir[subdir])
1454 self._all_packages.add(cipd_package)
1455 self._packages_by_subdir[subdir].append(cipd_package)
1456 return cipd_package
1457
1458 def packages(self, subdir):
1459 """Get the list of configured packages for the given subdir."""
1460 return list(self._packages_by_subdir[subdir])
1461
1462 def clobber(self):
1463 """Remove the .cipd directory.
1464
1465 This is useful for forcing ensure to redownload and reinitialize all
1466 packages.
1467 """
1468 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001469 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001470 try:
1471 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1472 except OSError:
1473 if os.path.exists(cipd_cache_dir):
1474 raise
1475
1476 @contextlib.contextmanager
1477 def _create_ensure_file(self):
1478 try:
Edward Lesmes05934952019-12-19 20:38:09 +00001479 contents = '$ParanoidMode CheckPresence\n\n'
1480 for subdir, packages in sorted(self._packages_by_subdir.items()):
1481 contents += '@Subdir %s\n' % subdir
1482 for package in sorted(packages, key=lambda p: p.name):
1483 contents += '%s %s\n' % (package.name, package.version)
1484 contents += '\n'
John Budorick0f7b2002018-01-19 15:46:17 -08001485 ensure_file = None
1486 with tempfile.NamedTemporaryFile(
Edward Lesmes05934952019-12-19 20:38:09 +00001487 suffix='.ensure', delete=False, mode='wb') as ensure_file:
1488 ensure_file.write(contents.encode('utf-8', 'replace'))
John Budorick0f7b2002018-01-19 15:46:17 -08001489 yield ensure_file.name
1490 finally:
1491 if ensure_file is not None and os.path.exists(ensure_file.name):
1492 os.remove(ensure_file.name)
1493
1494 def ensure(self):
1495 """Run `cipd ensure`."""
1496 with self._mutator_lock:
1497 with self._create_ensure_file() as ensure_file:
1498 cmd = [
1499 'cipd', 'ensure',
1500 '-log-level', 'error',
1501 '-root', self.root_dir,
1502 '-ensure-file', ensure_file,
1503 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001504 gclient_utils.CheckCallAndFilter(
1505 cmd, print_stdout=True, show_header=True)
John Budorick0f7b2002018-01-19 15:46:17 -08001506
John Budorickd3ba72b2018-03-20 12:27:42 -07001507 def run(self, command):
1508 if command == 'update':
1509 self.ensure()
1510 elif command == 'revert':
1511 self.clobber()
1512 self.ensure()
1513
John Budorick0f7b2002018-01-19 15:46:17 -08001514 def created_package(self, package):
1515 """Checks whether this root created the given package.
1516
1517 Args:
1518 package: CipdPackage; the package to check.
1519 Returns:
1520 bool; whether this root created the given package.
1521 """
1522 return package in self._all_packages
1523
1524 @property
1525 def root_dir(self):
1526 return self._root_dir
1527
1528 @property
1529 def service_url(self):
1530 return self._service_url
1531
1532
1533class CipdWrapper(SCMWrapper):
1534 """Wrapper for CIPD.
1535
1536 Currently only supports chrome-infra-packages.appspot.com.
1537 """
John Budorick3929e9e2018-02-04 18:18:07 -08001538 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001539
1540 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1541 out_cb=None, root=None, package=None):
1542 super(CipdWrapper, self).__init__(
1543 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1544 out_cb=out_cb)
1545 assert root.created_package(package)
1546 self._package = package
1547 self._root = root
1548
1549 #override
1550 def GetCacheMirror(self):
1551 return None
1552
1553 #override
1554 def GetActualRemoteURL(self, options):
1555 return self._root.service_url
1556
1557 #override
1558 def DoesRemoteURLMatch(self, options):
1559 del options
1560 return True
1561
1562 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001563 """Does nothing.
1564
1565 CIPD packages should be reverted at the root by running
1566 `CipdRoot.run('revert')`.
1567 """
1568 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001569
1570 def diff(self, options, args, file_list):
1571 """CIPD has no notion of diffing."""
1572 pass
1573
1574 def pack(self, options, args, file_list):
1575 """CIPD has no notion of diffing."""
1576 pass
1577
1578 def revinfo(self, options, args, file_list):
1579 """Grab the instance ID."""
1580 try:
1581 tmpdir = tempfile.mkdtemp()
1582 describe_json_path = os.path.join(tmpdir, 'describe.json')
1583 cmd = [
1584 'cipd', 'describe',
1585 self._package.name,
1586 '-log-level', 'error',
1587 '-version', self._package.version,
1588 '-json-output', describe_json_path
1589 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001590 gclient_utils.CheckCallAndFilter(cmd)
John Budorick0f7b2002018-01-19 15:46:17 -08001591 with open(describe_json_path) as f:
1592 describe_json = json.load(f)
1593 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1594 finally:
1595 gclient_utils.rmtree(tmpdir)
1596
1597 def status(self, options, args, file_list):
1598 pass
1599
1600 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001601 """Does nothing.
1602
1603 CIPD packages should be updated at the root by running
1604 `CipdRoot.run('update')`.
1605 """
1606 pass