blob: 151503b75448a23442f86baea1dcc65ab3bb5afd [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):
399 target_rev = remote_ref
400 elif not scm.GIT.IsValidRevision(self.checkout_path, target_rev):
401 # Fetch |target_rev| if it's not already available.
402 url, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmes07a68342021-04-20 23:39:30 +0000403 mirror = self._GetMirror(url, options, target_rev, target_rev)
Edward Lemur3acbc742019-05-30 17:57:35 +0000404 if mirror:
405 rev_type = 'branch' if target_rev.startswith('refs/') else 'hash'
406 self._UpdateMirrorIfNotContains(mirror, options, rev_type, target_rev)
407 self._Fetch(options, refspec=target_rev)
Edward Lemura0ffbe42019-05-01 16:52:18 +0000408
Edward Lemur3acbc742019-05-30 17:57:35 +0000409 self.Print('===Applying patch===')
410 self.Print('Revision to patch is %r @ %r.' % (patch_repo, patch_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000411 self.Print('Current dir is %r' % self.checkout_path)
Edward Lesmesc621b212018-03-21 20:26:56 -0400412 self._Capture(['reset', '--hard'])
danakjd5c0b562019-11-08 17:27:47 +0000413 self._Capture(['fetch', '--no-tags', patch_repo, patch_rev])
Edward Lemurca7d8812018-07-24 17:42:45 +0000414 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400415
Edward Lemur3acbc742019-05-30 17:57:35 +0000416 if not options.rebase_patch_ref:
417 self._Capture(['checkout', patch_rev])
Robert Iannuccic39a7782019-11-01 18:30:33 +0000418 # Adjust base_rev to be the first parent of our checked out patch ref;
419 # This will allow us to correctly extend `file_list`, and will show the
420 # correct file-list to programs which do `git diff --cached` expecting to
421 # see the patch diff.
422 base_rev = self._Capture(['rev-parse', patch_rev+'~'])
423
Edward Lemur3acbc742019-05-30 17:57:35 +0000424 else:
Robert Iannuccic39a7782019-11-01 18:30:33 +0000425 self.Print('Will cherrypick %r .. %r on top of %r.' % (
426 target_rev, patch_rev, base_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000427 try:
428 if scm.GIT.IsAncestor(self.checkout_path, patch_rev, target_rev):
429 # If |patch_rev| is an ancestor of |target_rev|, check it out.
Edward Lemurca7d8812018-07-24 17:42:45 +0000430 self._Capture(['checkout', patch_rev])
431 else:
432 # If a change was uploaded on top of another change, which has already
433 # landed, one of the commits in the cherry-pick range will be
434 # redundant, since it has already landed and its changes incorporated
435 # in the tree.
436 # We pass '--keep-redundant-commits' to ignore those changes.
Edward Lemur3acbc742019-05-30 17:57:35 +0000437 self._Capture(['cherry-pick', target_rev + '..' + patch_rev,
Edward Lemurca7d8812018-07-24 17:42:45 +0000438 '--keep-redundant-commits'])
439
Edward Lemur3acbc742019-05-30 17:57:35 +0000440 except subprocess2.CalledProcessError as e:
441 self.Print('Failed to apply patch.')
442 self.Print('Revision to patch was %r @ %r.' % (patch_repo, patch_rev))
443 self.Print('Tried to cherrypick %r .. %r on top of %r.' % (
444 target_rev, patch_rev, base_rev))
445 self.Print('Current dir is %r' % self.checkout_path)
446 self.Print('git returned non-zero exit status %s:\n%s' % (
Edward Lemur979fa782019-08-13 22:44:05 +0000447 e.returncode, e.stderr.decode('utf-8')))
Edward Lemur3acbc742019-05-30 17:57:35 +0000448 # Print the current status so that developers know what changes caused
449 # the patch failure, since git cherry-pick doesn't show that
450 # information.
451 self.Print(self._Capture(['status']))
452 try:
453 self._Capture(['cherry-pick', '--abort'])
454 except subprocess2.CalledProcessError:
455 pass
456 raise
Edward Lemurca7d8812018-07-24 17:42:45 +0000457
Edward Lemur3acbc742019-05-30 17:57:35 +0000458 if file_list is not None:
459 file_list.extend(self._GetDiffFilenames(base_rev))
Edward Lemurca7d8812018-07-24 17:42:45 +0000460
Edward Lesmesc621b212018-03-21 20:26:56 -0400461 if options.reset_patch_ref:
462 self._Capture(['reset', '--soft', base_rev])
463
msb@chromium.orge28e4982009-09-25 20:51:45 +0000464 def update(self, options, args, file_list):
465 """Runs git to update or transparently checkout the working copy.
466
467 All updated files will be appended to file_list.
468
469 Raises:
470 Error: if can't get URL for relative path.
471 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000472 if args:
473 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
474
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000475 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000476
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000477 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000478 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000479 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000480 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000481 # Override the revision number.
482 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000483 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000484 # Check again for a revision in case an initial ref was specified
485 # in the url, for example bla.git@refs/heads/custombranch
486 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000487 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000488 if not revision:
Josip Sokcevic091f5ac2021-01-14 23:14:21 +0000489 # If a dependency is not pinned, track the default remote branch.
490 revision = scm.GIT.GetRemoteHeadRef(self.checkout_path, self.url,
491 self.remote)
Edward Lesmes4ea67bb2021-04-20 17:33:52 +0000492 if revision.startswith('origin/'):
493 revision = 'refs/remotes/' + revision
msb@chromium.orge28e4982009-09-25 20:51:45 +0000494
Tomasz Wiszkowskid4e66882021-08-19 21:35:09 +0000495 if managed and platform.system() == 'Windows':
szager@chromium.org8a139702014-06-20 15:55:01 +0000496 self._DisableHooks()
497
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000498 printed_path = False
499 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000500 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700501 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000502 verbose = ['--verbose']
503 printed_path = True
504
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000505 revision_ref = revision
506 if ':' in revision:
507 revision_ref, _, revision = revision.partition(':')
508
Edward Lesmes8073a502020-04-15 02:11:14 +0000509 if revision_ref.startswith('refs/branch-heads'):
510 options.with_branch_heads = True
511
Edward Lesmes07a68342021-04-20 23:39:30 +0000512 mirror = self._GetMirror(url, options, revision, revision_ref)
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000513 if mirror:
514 url = mirror.mirror_path
Edward Lemurdb5c5ad2019-03-07 16:00:24 +0000515
John Budorick882c91e2018-07-12 22:11:41 +0000516 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
517 if remote_ref:
518 # Rewrite remote refs to their local equivalents.
519 revision = ''.join(remote_ref)
520 rev_type = "branch"
521 elif revision.startswith('refs/'):
522 # Local branch? We probably don't want to support, since DEPS should
523 # always specify branches as they are in the upstream repo.
524 rev_type = "branch"
525 else:
526 # hash is also a tag, only make a distinction at checkout
527 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000528
primiano@chromium.org1c127382015-02-17 11:15:40 +0000529 # If we are going to introduce a new project, there is a possibility that
530 # we are syncing back to a state where the project was originally a
531 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
532 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
533 # In such case, we might have a backup of the former .git folder, which can
534 # be used to avoid re-fetching the entire repo again (useful for bisects).
535 backup_dir = self.GetGitBackupDirPath()
536 target_dir = os.path.join(self.checkout_path, '.git')
537 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
538 gclient_utils.safe_makedirs(self.checkout_path)
539 os.rename(backup_dir, target_dir)
540 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800541 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000542
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000543 if (not os.path.exists(self.checkout_path) or
544 (os.path.isdir(self.checkout_path) and
545 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000546 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000547 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000548 try:
John Budorick882c91e2018-07-12 22:11:41 +0000549 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000550 except subprocess2.CalledProcessError:
551 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000552 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000553 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800554 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20 +0000555 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambrec2f74c12019-03-19 05:55:53 +0000556 file_list.extend(
Edward Lemur979fa782019-08-13 22:44:05 +0000557 [os.path.join(self.checkout_path, f) for f in files])
John Budorick21a51b32018-09-19 19:39:20 +0000558 if mirror:
559 self._Capture(
560 ['remote', 'set-url', '--push', 'origin', mirror.url])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000561 if not verbose:
562 # Make the output a little prettier. It's nice to have some whitespace
563 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000564 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000565 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000566
John Budorick21a51b32018-09-19 19:39:20 +0000567 if mirror:
568 self._Capture(
569 ['remote', 'set-url', '--push', 'origin', mirror.url])
570
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000571 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000572 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000573 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
574 return self._Capture(['rev-parse', '--verify', 'HEAD'])
575
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000576 self._maybe_break_locks(options)
577
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000578 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000579 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000580
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000581 # See if the url has changed (the unittests use git://foo for the url, let
582 # that through).
583 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
584 return_early = False
585 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
586 # unit test pass. (and update the comment above)
587 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
588 # This allows devs to use experimental repos which have a different url
589 # but whose branch(s) are the same as official repos.
Raul Tambrecd862e32019-05-10 21:19:00 +0000590 if (current_url.rstrip('/') != url.rstrip('/') and url != 'git://foo' and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000591 subprocess2.capture(
Aaron Gableac9b0f32019-04-18 17:38:37 +0000592 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000593 cwd=self.checkout_path).strip() != 'False'):
Anthony Polito486f1812020-08-04 23:40:33 +0000594 self.Print('_____ switching %s from %s to new upstream %s' % (
595 self.relpath, current_url, url))
iannucci@chromium.org78514212014-08-20 23:08:00 +0000596 if not (options.force or options.reset):
597 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700598 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000599 # Switch over to the new upstream
600 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000601 if mirror:
602 with open(os.path.join(
603 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
604 'w') as fh:
605 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000606 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
607 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000608
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000609 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000610 else:
John Budorick882c91e2018-07-12 22:11:41 +0000611 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000612
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000613 if return_early:
614 return self._Capture(['rev-parse', '--verify', 'HEAD'])
615
msb@chromium.org5bde4852009-12-14 16:47:12 +0000616 cur_branch = self._GetCurrentBranch()
617
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000618 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000619 # 0) HEAD is detached. Probably from our initial clone.
620 # - make sure HEAD is contained by a named ref, then update.
621 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700622 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000623 # - try to rebase onto the new hash or branch
624 # 2) current branch is tracking a remote branch with local committed
625 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000626 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000627 # 3) current branch is tracking a remote branch w/or w/out changes, and
628 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000629 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000630 # 4) current branch is tracking a remote branch, but DEPS switches to a
631 # different remote branch, and
632 # a) current branch has no local changes, and --force:
633 # - checkout new branch
634 # b) current branch has local changes, and --force and --reset:
635 # - checkout new branch
636 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000637
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000638 # GetUpstreamBranch returns something like 'refs/remotes/origin/main' for
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000639 # a tracking branch
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000640 # or 'main' if not a tracking branch (it's based on a specific rev/hash)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000641 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000642 if cur_branch is None:
643 upstream_branch = None
644 current_type = "detached"
645 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000646 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000647 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
648 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
649 current_type = "hash"
650 logging.debug("Current branch is not tracking an upstream (remote)"
651 " branch.")
652 elif upstream_branch.startswith('refs/remotes'):
653 current_type = "branch"
654 else:
655 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000656
Edward Lemur579c9862018-07-13 23:17:51 +0000657 self._SetFetchConfig(options)
Edward Lemur579c9862018-07-13 23:17:51 +0000658
Michael Spang73fac912019-03-08 18:44:19 +0000659 # Fetch upstream if we don't already have |revision|.
John Budorick882c91e2018-07-12 22:11:41 +0000660 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
Michael Spang73fac912019-03-08 18:44:19 +0000661 self._Fetch(options, prune=options.force)
662
663 if not scm.GIT.IsValidRevision(self.checkout_path, revision,
664 sha_only=True):
665 # Update the remotes first so we have all the refs.
666 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
667 cwd=self.checkout_path)
668 if verbose:
669 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000670
John Budorick882c91e2018-07-12 22:11:41 +0000671 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200672
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000673 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000674 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000675 target = 'HEAD'
676 if options.upstream and upstream_branch:
677 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800678 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000679
msb@chromium.org786fb682010-06-02 15:16:23 +0000680 if current_type == 'detached':
681 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800682 # We just did a Scrub, this is as clean as it's going to get. In
683 # particular if HEAD is a commit that contains two versions of the same
684 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
685 # to actually "Clean" the checkout; that commit is uncheckoutable on this
686 # system. The best we can do is carry forward to the checkout step.
687 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000688 self._CheckClean(revision)
689 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000690 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000691 self.Print('Up-to-date; skipping checkout.')
692 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000693 # 'git checkout' may need to overwrite existing untracked files. Allow
694 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000695 self._Checkout(
696 options,
John Budorick882c91e2018-07-12 22:11:41 +0000697 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000698 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000699 quiet=True,
700 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000701 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000702 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000703 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000704 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700705 # Can't find a merge-base since we don't know our upstream. That makes
706 # this command VERY likely to produce a rebase failure. For now we
707 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000708 upstream_branch = self.remote
709 if options.revision or deps_revision:
710 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700711 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700712 printed_path=printed_path, merge=options.merge)
713 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000714 elif rev_type == 'hash':
715 # case 2
716 self._AttemptRebase(upstream_branch, file_list, options,
717 newbase=revision, printed_path=printed_path,
718 merge=options.merge)
719 printed_path = True
720 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000721 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000722 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000723 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000724 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000725 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000726 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000727 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000728 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
729 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000730 force_switch = False
731 if options.force:
732 try:
John Budorick882c91e2018-07-12 22:11:41 +0000733 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000734 # case 4a
735 force_switch = True
736 except gclient_utils.Error as e:
737 if options.reset:
738 # case 4b
739 force_switch = True
740 else:
741 switch_error = '%s\n%s' % (e.message, switch_error)
742 if force_switch:
743 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000744 (upstream_branch, new_base))
745 switch_branch = 'gclient_' + remote_ref[1]
746 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000747 self._Checkout(options, switch_branch, force=True, quiet=True)
748 else:
749 # case 4c
750 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000751 else:
752 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800753 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000754 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000755 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000756 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000757 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000758 if options.merge:
759 merge_args.append('--ff')
760 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000761 merge_args.append('--ff-only')
762 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000763 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000764 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700765 rebase_files = []
Edward Lemur979fa782019-08-13 22:44:05 +0000766 if re.match(b'fatal: Not possible to fast-forward, aborting.',
767 e.stderr):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000768 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000769 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700770 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000771 printed_path = True
772 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000773 if not options.auto_rebase:
774 try:
775 action = self._AskForData(
776 'Cannot %s, attempt to rebase? '
777 '(y)es / (q)uit / (s)kip : ' %
778 ('merge' if options.merge else 'fast-forward merge'),
779 options)
780 except ValueError:
781 raise gclient_utils.Error('Invalid Character')
782 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700783 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000784 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000785 printed_path = True
786 break
787 elif re.match(r'quit|q', action, re.I):
788 raise gclient_utils.Error("Can't fast-forward, please merge or "
789 "rebase manually.\n"
790 "cd %s && git " % self.checkout_path
791 + "rebase %s" % upstream_branch)
792 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000793 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000794 return
795 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000796 self.Print('Input not recognized')
Edward Lemur979fa782019-08-13 22:44:05 +0000797 elif re.match(b"error: Your local changes to '.*' would be "
798 b"overwritten by merge. Aborting.\nPlease, commit your "
799 b"changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000800 e.stderr):
801 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000802 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700803 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000804 printed_path = True
Edward Lemur979fa782019-08-13 22:44:05 +0000805 raise gclient_utils.Error(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000806 else:
807 # Some other problem happened with the merge
808 logging.error("Error during fast-forward merge in %s!" % self.relpath)
Edward Lemur979fa782019-08-13 22:44:05 +0000809 self.Print(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000810 raise
811 else:
812 # Fast-forward merge was successful
813 if not re.match('Already up-to-date.', merge_output) or verbose:
814 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700815 self.Print('_____ %s at %s' % (self.relpath, revision),
816 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000817 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000818 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000819 if not verbose:
820 # Make the output a little prettier. It's nice to have some
821 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000822 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000823
agablec3937b92016-10-25 10:13:03 -0700824 if file_list is not None:
825 file_list.extend(
826 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000827
828 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000829 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700830 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000831 '\nConflict while rebasing this branch.\n'
832 'Fix the conflict and run gclient again.\n'
833 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700834 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000835
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000836 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000837 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
838 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000839
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000840 # If --reset and --delete_unversioned_trees are specified, remove any
841 # untracked directories.
842 if options.reset and options.delete_unversioned_trees:
843 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
844 # merge-base by default), so doesn't include untracked files. So we use
845 # 'git ls-files --directory --others --exclude-standard' here directly.
846 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800847 ['-c', 'core.quotePath=false', 'ls-files',
848 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000849 self.checkout_path)
850 for path in (p for p in paths.splitlines() if p.endswith('/')):
851 full_path = os.path.join(self.checkout_path, path)
852 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000853 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000854 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000855
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000856 return self._Capture(['rev-parse', '--verify', 'HEAD'])
857
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000858 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000859 """Reverts local modifications.
860
861 All reverted files will be appended to file_list.
862 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000863 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000864 # revert won't work if the directory doesn't exist. It needs to
865 # checkout instead.
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000866 self.Print('_____ %s is missing, syncing instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000867 # Don't reuse the args.
868 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000869
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000870 default_rev = "refs/heads/main"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000871 if options.upstream:
872 if self._GetCurrentBranch():
873 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
874 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000875 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000876 if not deps_revision:
877 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000878 if deps_revision.startswith('refs/heads/'):
879 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700880 try:
881 deps_revision = self.GetUsableRev(deps_revision, options)
882 except NoUsableRevError as e:
883 # If the DEPS entry's url and hash changed, try to update the origin.
884 # See also http://crbug.com/520067.
John Budorickd94f8ea2020-03-27 15:55:24 +0000885 logging.warning(
886 "Couldn't find usable revision, will retrying to update instead: %s",
smutae7ea312016-07-18 11:59:41 -0700887 e.message)
888 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000889
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000890 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800891 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000892
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800893 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000894 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000895
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000896 if file_list is not None:
897 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
898
899 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000900 """Returns revision"""
901 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000902
msb@chromium.orge28e4982009-09-25 20:51:45 +0000903 def runhooks(self, options, args, file_list):
904 self.status(options, args, file_list)
905
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000906 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000907 """Display status information."""
908 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000909 self.Print('________ couldn\'t run status in %s:\n'
910 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000911 else:
Anthony Politobb457342019-11-15 22:26:01 +0000912 merge_base = []
913 if self.url:
914 _, base_rev = gclient_utils.SplitUrlRevision(self.url)
915 if base_rev:
916 merge_base = [base_rev]
Aaron Gablef4068aa2017-12-12 15:14:09 -0800917 self._Run(
918 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
Edward Lemur24146be2019-08-01 21:44:52 +0000919 options, always_show_header=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000920 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800921 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000922 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000923
smutae7ea312016-07-18 11:59:41 -0700924 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700925 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700926 sha1 = None
927 if not os.path.isdir(self.checkout_path):
928 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800929 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700930
931 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
932 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700933 else:
agable41e3a6c2016-10-20 11:36:56 -0700934 # May exist in origin, but we don't have it yet, so fetch and look
935 # again.
936 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700937 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
938 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700939
940 if not sha1:
941 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800942 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700943
944 return sha1
945
primiano@chromium.org1c127382015-02-17 11:15:40 +0000946 def GetGitBackupDirPath(self):
947 """Returns the path where the .git folder for the current project can be
948 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
949 return os.path.join(self._root_dir,
950 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
951
Edward Lesmes07a68342021-04-20 23:39:30 +0000952 def _GetMirror(self, url, options, revision=None, revision_ref=None):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000953 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000954 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000955 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000956 mirror_kwargs = {
957 'print_func': self.filter,
Edward Lesmes07a68342021-04-20 23:39:30 +0000958 'refs': [],
959 'commits': [],
hinoka@google.comb1b54572014-04-16 22:29:23 +0000960 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000961 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
962 mirror_kwargs['refs'].append('refs/branch-heads/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000963 elif revision_ref and revision_ref.startswith('refs/branch-heads/'):
964 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000965 if hasattr(options, 'with_tags') and options.with_tags:
966 mirror_kwargs['refs'].append('refs/tags/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000967 elif revision_ref and revision_ref.startswith('refs/tags/'):
968 mirror_kwargs['refs'].append(revision_ref)
Edward Lesmes07a68342021-04-20 23:39:30 +0000969 if revision and not revision.startswith('refs/'):
970 mirror_kwargs['commits'].append(revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000971 return git_cache.Mirror(url, **mirror_kwargs)
972
John Budorick882c91e2018-07-12 22:11:41 +0000973 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800974 """Update a git mirror by fetching the latest commits from the remote,
975 unless mirror already contains revision whose type is sha1 hash.
976 """
John Budorick882c91e2018-07-12 22:11:41 +0000977 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800978 if options.verbose:
979 self.Print('skipping mirror update, it has rev=%s already' % revision,
980 timestamp=False)
981 return
982
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000983 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000984 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000985 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000986 depth = 10
987 else:
988 depth = 10000
989 else:
990 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000991 mirror.populate(verbose=options.verbose,
992 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000993 depth=depth,
Vadim Shtayura08049e22017-10-11 00:14:52 +0000994 lock_timeout=getattr(options, 'lock_timeout', 0))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000995
John Budorick882c91e2018-07-12 22:11:41 +0000996 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000997 """Clone a git repository from the given URL.
998
msb@chromium.org786fb682010-06-02 15:16:23 +0000999 Once we've cloned the repo, we checkout a working branch if the specified
1000 revision is a branch head. If it is a tag or a specific commit, then we
1001 leave HEAD detached as it makes future updates simpler -- in this case the
1002 user should first create a new branch or switch to an existing branch before
1003 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001004 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001005 # git clone doesn't seem to insert a newline properly before printing
1006 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001007 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +00001008 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +00001009 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001010 if self.cache_dir:
1011 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001012 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001013 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001014 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +00001015 # If the parent directory does not exist, Git clone on Windows will not
1016 # create it, so we need to do it manually.
1017 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001018 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001019
1020 template_dir = None
1021 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +00001022 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001023 # In the case of a subproject, the pinned sha is not necessarily the
1024 # head of the remote branch (so we can't just use --depth=N). Instead,
1025 # we tell git to fetch all the remote objects from SHA..HEAD by means of
1026 # a template git dir which has a 'shallow' file pointing to the sha.
1027 template_dir = tempfile.mkdtemp(
1028 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
1029 dir=parent_dir)
1030 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
1031 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
1032 template_file.write(revision)
1033 clone_cmd.append('--template=' + template_dir)
1034 else:
1035 # Otherwise, we're just interested in the HEAD. Just use --depth.
1036 clone_cmd.append('--depth=1')
1037
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001038 tmp_dir = tempfile.mkdtemp(
1039 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
1040 dir=parent_dir)
1041 try:
1042 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001043 if self.print_outbuf:
1044 print_stdout = True
Edward Lemur24146be2019-08-01 21:44:52 +00001045 filter_fn = None
Edward Lemur231f5ea2018-01-31 19:02:36 +01001046 else:
1047 print_stdout = False
Edward Lemur24146be2019-08-01 21:44:52 +00001048 filter_fn = self.filter
Edward Lemur231f5ea2018-01-31 19:02:36 +01001049 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
Edward Lemur24146be2019-08-01 21:44:52 +00001050 print_stdout=print_stdout, filter_fn=filter_fn)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001051 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +00001052 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
1053 os.path.join(self.checkout_path, '.git'))
Edward Lesmesd4e20f22020-07-15 21:11:08 +00001054 # TODO(https://github.com/git-for-windows/git/issues/2569): Remove once
1055 # fixed.
1056 if sys.platform.startswith('win'):
1057 try:
1058 self._Run(['config', '--unset', 'core.worktree'], options,
1059 cwd=self.checkout_path)
1060 except subprocess2.CalledProcessError:
1061 pass
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001062 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001063 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001064 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001065 finally:
1066 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001067 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001068 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001069 if template_dir:
1070 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +00001071 self._SetFetchConfig(options)
1072 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001073 revision = self._AutoFetchRef(options, revision)
1074 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1075 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +00001076 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +00001077 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001078 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +00001079 ('Checked out %s to a detached HEAD. Before making any commits\n'
1080 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1081 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +00001082 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001083
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001084 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001085 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001086 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001087 raise gclient_utils.Error("Background task requires input. Rerun "
1088 "gclient with --jobs=1 so that\n"
1089 "interaction is possible.")
Edward Lesmesae3586b2020-03-23 21:21:14 +00001090 return gclient_utils.AskForData(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001091
1092
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001093 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001094 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001095 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001096 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001097 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001098 revision = upstream
1099 if newbase:
1100 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001101 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001102 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001103 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001104 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001105 printed_path = True
1106 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001107 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001108
1109 if merge:
1110 merge_output = self._Capture(['merge', revision])
1111 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001112 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001113 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001114
1115 # Build the rebase command here using the args
1116 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1117 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001118 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001119 rebase_cmd.append('--verbose')
1120 if newbase:
1121 rebase_cmd.extend(['--onto', newbase])
1122 rebase_cmd.append(upstream)
1123 if branch:
1124 rebase_cmd.append(branch)
1125
1126 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001127 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
Raul Tambreb946b232019-03-26 14:48:46 +00001128 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001129 if (re.match(br'cannot rebase: you have unstaged changes', e.stderr) or
1130 re.match(br'cannot rebase: your index contains uncommitted changes',
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001131 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001132 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001133 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001134 'Cannot rebase because of unstaged changes.\n'
1135 '\'git reset --hard HEAD\' ?\n'
1136 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001137 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001138 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001139 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001140 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001141 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001142 break
1143 elif re.match(r'quit|q', rebase_action, re.I):
1144 raise gclient_utils.Error("Please merge or rebase manually\n"
1145 "cd %s && git " % self.checkout_path
1146 + "%s" % ' '.join(rebase_cmd))
1147 elif re.match(r'show|s', rebase_action, re.I):
Edward Lemur979fa782019-08-13 22:44:05 +00001148 self.Print('%s' % e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001149 continue
1150 else:
1151 gclient_utils.Error("Input not recognized")
1152 continue
Edward Lemur979fa782019-08-13 22:44:05 +00001153 elif re.search(br'^CONFLICT', e.stdout, re.M):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001154 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1155 "Fix the conflict and run gclient again.\n"
1156 "See 'man git-rebase' for details.\n")
1157 else:
Edward Lemur979fa782019-08-13 22:44:05 +00001158 self.Print(e.stdout.decode('utf-8').strip())
1159 self.Print('Rebase produced error output:\n%s' %
1160 e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001161 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1162 "manually.\ncd %s && git " %
1163 self.checkout_path
1164 + "%s" % ' '.join(rebase_cmd))
1165
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001166 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001167 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001168 # Make the output a little prettier. It's nice to have some
1169 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001170 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001171
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001172 @staticmethod
1173 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001174 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1175 if not ok:
1176 raise gclient_utils.Error('git version %s < minimum required %s' %
1177 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001178
John Budorick882c91e2018-07-12 22:11:41 +00001179 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001180 # Special case handling if all 3 conditions are met:
1181 # * the mirros have recently changed, but deps destination remains same,
1182 # * the git histories of mirrors are conflicting.
1183 # * git cache is used
1184 # This manifests itself in current checkout having invalid HEAD commit on
1185 # most git operations. Since git cache is used, just deleted the .git
1186 # folder, and re-create it by cloning.
1187 try:
1188 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1189 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001190 if (b'fatal: bad object HEAD' in e.stderr
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001191 and self.cache_dir and self.cache_dir in url):
1192 self.Print((
1193 'Likely due to DEPS change with git cache_dir, '
1194 'the current commit points to no longer existing object.\n'
1195 '%s' % e)
1196 )
1197 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001198 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001199 else:
1200 raise
1201
msb@chromium.org786fb682010-06-02 15:16:23 +00001202 def _IsRebasing(self):
1203 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1204 # have a plumbing command to determine whether a rebase is in progress, so
1205 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1206 g = os.path.join(self.checkout_path, '.git')
1207 return (
1208 os.path.isdir(os.path.join(g, "rebase-merge")) or
1209 os.path.isdir(os.path.join(g, "rebase-apply")))
1210
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001211 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001212 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1213 if os.path.exists(lockfile):
1214 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001215 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001216 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1217 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001218 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001219
msb@chromium.org786fb682010-06-02 15:16:23 +00001220 # Make sure the tree is clean; see git-rebase.sh for reference
1221 try:
1222 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001223 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001224 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001225 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001226 '\tYou have unstaged changes.\n'
1227 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001228 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001229 try:
1230 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001231 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001232 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001233 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001234 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001235 '\tYour index contains uncommitted changes\n'
1236 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001237 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001238
agable83faed02016-10-24 14:37:10 -07001239 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001240 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1241 # reference by a commit). If not, error out -- most likely a rebase is
1242 # in progress, try to detect so we can give a better error.
1243 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001244 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1245 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001246 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001247 # Commit is not contained by any rev. See if the user is rebasing:
1248 if self._IsRebasing():
1249 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001250 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001251 '\tAlready in a conflict, i.e. (no branch).\n'
1252 '\tFix the conflict and run gclient again.\n'
1253 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1254 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001255 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001256 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001257 name = ('saved-by-gclient-' +
1258 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001259 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001260 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001261 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001262
msb@chromium.org5bde4852009-12-14 16:47:12 +00001263 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001264 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001265 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001266 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001267 return None
1268 return branch
1269
borenet@google.comc3e09d22014-04-10 13:58:18 +00001270 def _Capture(self, args, **kwargs):
Mike Frysinger286fb162019-09-30 03:14:10 +00001271 set_git_dir = 'cwd' not in kwargs
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001272 kwargs.setdefault('cwd', self.checkout_path)
1273 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001274 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001275 env = scm.GIT.ApplyEnvVars(kwargs)
Mike Frysinger286fb162019-09-30 03:14:10 +00001276 # If an explicit cwd isn't set, then default to the .git/ subdir so we get
1277 # stricter behavior. This can be useful in cases of slight corruption --
1278 # we don't accidentally go corrupting parent git checks too. See
1279 # https://crbug.com/1000825 for an example.
1280 if set_git_dir:
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001281 git_dir = os.path.abspath(os.path.join(self.checkout_path, '.git'))
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001282 # Depending on how the .gclient file was defined, self.checkout_path
1283 # might be set to a unicode string, not a regular string; on Windows
1284 # Python2, we can't set env vars to be unicode strings, so we
1285 # forcibly cast the value to a string before setting it.
1286 env.setdefault('GIT_DIR', str(git_dir))
Raul Tambrecd862e32019-05-10 21:19:00 +00001287 ret = subprocess2.check_output(
1288 ['git'] + args, env=env, **kwargs).decode('utf-8')
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001289 if strip:
1290 ret = ret.strip()
Erik Chene16ffff2019-10-14 20:35:53 +00001291 self.Print('Finished running: %s %s' % ('git', ' '.join(args)))
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001292 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001293
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001294 def _Checkout(self, options, ref, force=False, quiet=None):
1295 """Performs a 'git-checkout' operation.
1296
1297 Args:
1298 options: The configured option set
1299 ref: (str) The branch/commit to checkout
Quinten Yearsley925cedb2020-04-13 17:49:39 +00001300 quiet: (bool/None) Whether or not the checkout should pass '--quiet'; if
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001301 'None', the behavior is inferred from 'options.verbose'.
1302 Returns: (str) The output of the checkout operation
1303 """
1304 if quiet is None:
1305 quiet = (not options.verbose)
1306 checkout_args = ['checkout']
1307 if force:
1308 checkout_args.append('--force')
1309 if quiet:
1310 checkout_args.append('--quiet')
1311 checkout_args.append(ref)
1312 return self._Capture(checkout_args)
1313
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001314 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1315 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001316 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemur9a5e3bd2019-04-02 23:37:45 +00001317 # When updating, the ref is modified to be a remote ref .
1318 # (e.g. refs/heads/NAME becomes refs/remotes/REMOTE/NAME).
1319 # Try to reverse that mapping.
1320 original_ref = scm.GIT.RemoteRefToRef(refspec, self.remote)
1321 if original_ref:
1322 refspec = original_ref + ':' + refspec
1323 # When a mirror is configured, it only fetches
1324 # refs/{heads,branch-heads,tags}/*.
1325 # If asked to fetch other refs, we must fetch those directly from the
1326 # repository, and not from the mirror.
1327 if not original_ref.startswith(
1328 ('refs/heads/', 'refs/branch-heads/', 'refs/tags/')):
1329 remote, _ = gclient_utils.SplitUrlRevision(self.url)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001330 fetch_cmd = cfg + [
1331 'fetch',
1332 remote or self.remote,
1333 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001334 if refspec:
1335 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001336
1337 if prune:
1338 fetch_cmd.append('--prune')
1339 if options.verbose:
1340 fetch_cmd.append('--verbose')
danakjd5c0b562019-11-08 17:27:47 +00001341 if not hasattr(options, 'with_tags') or not options.with_tags:
1342 fetch_cmd.append('--no-tags')
dnj@chromium.org680f2172014-06-25 00:39:32 +00001343 elif quiet:
1344 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001345 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001346
Edward Lemur579c9862018-07-13 23:17:51 +00001347 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001348 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1349 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001350 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001351 try:
1352 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1353 options)
1354 self._Run(['config', 'remote.%s.fetch' % self.remote,
1355 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1356 except subprocess2.CalledProcessError as e:
1357 # If exit code was 5, it means we attempted to unset a config that
1358 # didn't exist. Ignore it.
1359 if e.returncode != 5:
1360 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001361 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001362 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001363 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1364 '^\\+refs/branch-heads/\\*:.*$']
1365 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001366 if hasattr(options, 'with_tags') and options.with_tags:
1367 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1368 '+refs/tags/*:refs/tags/*',
1369 '^\\+refs/tags/\\*:.*$']
1370 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001371
John Budorick882c91e2018-07-12 22:11:41 +00001372 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001373 """Attempts to fetch |revision| if not available in local repo.
1374
1375 Returns possibly updated revision."""
Edward Lemure0ba7b82020-03-11 20:31:32 +00001376 if not scm.GIT.IsValidRevision(self.checkout_path, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001377 self._Fetch(options, refspec=revision)
1378 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1379 return revision
1380
Edward Lemur24146be2019-08-01 21:44:52 +00001381 def _Run(self, args, options, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001382 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001383 kwargs.setdefault('cwd', self.checkout_path)
Edward Lemur24146be2019-08-01 21:44:52 +00001384 kwargs.setdefault('filter_fn', self.filter)
1385 kwargs.setdefault('show_header', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001386 env = scm.GIT.ApplyEnvVars(kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +00001387
agable@chromium.org772efaf2014-04-01 02:35:44 +00001388 cmd = ['git'] + args
Edward Lemur24146be2019-08-01 21:44:52 +00001389 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001390
1391
1392class CipdPackage(object):
1393 """A representation of a single CIPD package."""
1394
John Budorickd3ba72b2018-03-20 12:27:42 -07001395 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001396 self._authority_for_subdir = authority_for_subdir
1397 self._name = name
1398 self._version = version
1399
1400 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001401 def authority_for_subdir(self):
1402 """Whether this package has authority to act on behalf of its subdir.
1403
1404 Some operations should only be performed once per subdirectory. A package
1405 that has authority for its subdirectory is the only package that should
1406 perform such operations.
1407
1408 Returns:
1409 bool; whether this package has subdir authority.
1410 """
1411 return self._authority_for_subdir
1412
1413 @property
1414 def name(self):
1415 return self._name
1416
1417 @property
1418 def version(self):
1419 return self._version
1420
1421
1422class CipdRoot(object):
1423 """A representation of a single CIPD root."""
1424 def __init__(self, root_dir, service_url):
1425 self._all_packages = set()
1426 self._mutator_lock = threading.Lock()
1427 self._packages_by_subdir = collections.defaultdict(list)
1428 self._root_dir = root_dir
1429 self._service_url = service_url
1430
1431 def add_package(self, subdir, package, version):
1432 """Adds a package to this CIPD root.
1433
1434 As far as clients are concerned, this grants both root and subdir authority
1435 to packages arbitrarily. (The implementation grants root authority to the
1436 first package added and subdir authority to the first package added for that
1437 subdir, but clients should not depend on or expect that behavior.)
1438
1439 Args:
1440 subdir: str; relative path to where the package should be installed from
1441 the cipd root directory.
1442 package: str; the cipd package name.
1443 version: str; the cipd package version.
1444 Returns:
1445 CipdPackage; the package that was created and added to this root.
1446 """
1447 with self._mutator_lock:
1448 cipd_package = CipdPackage(
1449 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001450 not self._packages_by_subdir[subdir])
1451 self._all_packages.add(cipd_package)
1452 self._packages_by_subdir[subdir].append(cipd_package)
1453 return cipd_package
1454
1455 def packages(self, subdir):
1456 """Get the list of configured packages for the given subdir."""
1457 return list(self._packages_by_subdir[subdir])
1458
1459 def clobber(self):
1460 """Remove the .cipd directory.
1461
1462 This is useful for forcing ensure to redownload and reinitialize all
1463 packages.
1464 """
1465 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001466 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001467 try:
1468 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1469 except OSError:
1470 if os.path.exists(cipd_cache_dir):
1471 raise
1472
1473 @contextlib.contextmanager
1474 def _create_ensure_file(self):
1475 try:
Edward Lesmes05934952019-12-19 20:38:09 +00001476 contents = '$ParanoidMode CheckPresence\n\n'
1477 for subdir, packages in sorted(self._packages_by_subdir.items()):
1478 contents += '@Subdir %s\n' % subdir
1479 for package in sorted(packages, key=lambda p: p.name):
1480 contents += '%s %s\n' % (package.name, package.version)
1481 contents += '\n'
John Budorick0f7b2002018-01-19 15:46:17 -08001482 ensure_file = None
1483 with tempfile.NamedTemporaryFile(
Edward Lesmes05934952019-12-19 20:38:09 +00001484 suffix='.ensure', delete=False, mode='wb') as ensure_file:
1485 ensure_file.write(contents.encode('utf-8', 'replace'))
John Budorick0f7b2002018-01-19 15:46:17 -08001486 yield ensure_file.name
1487 finally:
1488 if ensure_file is not None and os.path.exists(ensure_file.name):
1489 os.remove(ensure_file.name)
1490
1491 def ensure(self):
1492 """Run `cipd ensure`."""
1493 with self._mutator_lock:
1494 with self._create_ensure_file() as ensure_file:
1495 cmd = [
1496 'cipd', 'ensure',
1497 '-log-level', 'error',
1498 '-root', self.root_dir,
1499 '-ensure-file', ensure_file,
1500 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001501 gclient_utils.CheckCallAndFilter(
1502 cmd, print_stdout=True, show_header=True)
John Budorick0f7b2002018-01-19 15:46:17 -08001503
John Budorickd3ba72b2018-03-20 12:27:42 -07001504 def run(self, command):
1505 if command == 'update':
1506 self.ensure()
1507 elif command == 'revert':
1508 self.clobber()
1509 self.ensure()
1510
John Budorick0f7b2002018-01-19 15:46:17 -08001511 def created_package(self, package):
1512 """Checks whether this root created the given package.
1513
1514 Args:
1515 package: CipdPackage; the package to check.
1516 Returns:
1517 bool; whether this root created the given package.
1518 """
1519 return package in self._all_packages
1520
1521 @property
1522 def root_dir(self):
1523 return self._root_dir
1524
1525 @property
1526 def service_url(self):
1527 return self._service_url
1528
1529
1530class CipdWrapper(SCMWrapper):
1531 """Wrapper for CIPD.
1532
1533 Currently only supports chrome-infra-packages.appspot.com.
1534 """
John Budorick3929e9e2018-02-04 18:18:07 -08001535 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001536
1537 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1538 out_cb=None, root=None, package=None):
1539 super(CipdWrapper, self).__init__(
1540 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1541 out_cb=out_cb)
1542 assert root.created_package(package)
1543 self._package = package
1544 self._root = root
1545
1546 #override
1547 def GetCacheMirror(self):
1548 return None
1549
1550 #override
1551 def GetActualRemoteURL(self, options):
1552 return self._root.service_url
1553
1554 #override
1555 def DoesRemoteURLMatch(self, options):
1556 del options
1557 return True
1558
1559 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001560 """Does nothing.
1561
1562 CIPD packages should be reverted at the root by running
1563 `CipdRoot.run('revert')`.
1564 """
1565 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001566
1567 def diff(self, options, args, file_list):
1568 """CIPD has no notion of diffing."""
1569 pass
1570
1571 def pack(self, options, args, file_list):
1572 """CIPD has no notion of diffing."""
1573 pass
1574
1575 def revinfo(self, options, args, file_list):
1576 """Grab the instance ID."""
1577 try:
1578 tmpdir = tempfile.mkdtemp()
1579 describe_json_path = os.path.join(tmpdir, 'describe.json')
1580 cmd = [
1581 'cipd', 'describe',
1582 self._package.name,
1583 '-log-level', 'error',
1584 '-version', self._package.version,
1585 '-json-output', describe_json_path
1586 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001587 gclient_utils.CheckCallAndFilter(cmd)
John Budorick0f7b2002018-01-19 15:46:17 -08001588 with open(describe_json_path) as f:
1589 describe_json = json.load(f)
1590 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1591 finally:
1592 gclient_utils.rmtree(tmpdir)
1593
1594 def status(self, options, args, file_list):
1595 pass
1596
1597 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001598 """Does nothing.
1599
1600 CIPD packages should be updated at the root by running
1601 `CipdRoot.run('update')`.
1602 """
1603 pass