blob: 959d3dc79eeea3e37065ef9fcd1932f5f7fba024 [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
Ravi Mistryecda7822022-02-28 16:22:20 +000029import gerrit_util
szager@chromium.org848fd492014-04-09 19:06:44 +000030import git_cache
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000031import scm
borenet@google.comb2256212014-05-07 20:57:28 +000032import shutil
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000033import subprocess2
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000034
35
smutae7ea312016-07-18 11:59:41 -070036class NoUsableRevError(gclient_utils.Error):
37 """Raised if requested revision isn't found in checkout."""
38
39
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000040class DiffFiltererWrapper(object):
41 """Simple base class which tracks which file is being diffed and
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000042 replaces instances of its file name in the original and
agable41e3a6c2016-10-20 11:36:56 -070043 working copy lines of the git diff output."""
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000044 index_string = None
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000045 original_prefix = "--- "
46 working_prefix = "+++ "
47
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000048 def __init__(self, relpath, print_func):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000049 # Note that we always use '/' as the path separator to be
agable41e3a6c2016-10-20 11:36:56 -070050 # consistent with cygwin-style output on Windows
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000051 self._relpath = relpath.replace("\\", "/")
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000052 self._current_file = None
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000053 self._print_func = print_func
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000054
maruel@chromium.org6e29d572010-06-04 17:32:20 +000055 def SetCurrentFile(self, current_file):
56 self._current_file = current_file
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000057
iannucci@chromium.org3830a672013-02-19 20:15:14 +000058 @property
59 def _replacement_file(self):
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000060 return posixpath.join(self._relpath, self._current_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000061
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000062 def _Replace(self, line):
63 return line.replace(self._current_file, self._replacement_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000064
65 def Filter(self, line):
66 if (line.startswith(self.index_string)):
67 self.SetCurrentFile(line[len(self.index_string):])
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000068 line = self._Replace(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000069 else:
70 if (line.startswith(self.original_prefix) or
71 line.startswith(self.working_prefix)):
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000072 line = self._Replace(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000073 self._print_func(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000074
75
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000076class GitDiffFilterer(DiffFiltererWrapper):
77 index_string = "diff --git "
78
79 def SetCurrentFile(self, current_file):
80 # Get filename by parsing "a/<filename> b/<filename>"
81 self._current_file = current_file[:(len(current_file)/2)][2:]
82
83 def _Replace(self, line):
84 return re.sub("[a|b]/" + self._current_file, self._replacement_file, line)
85
86
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000087# SCMWrapper base class
88
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000089class SCMWrapper(object):
90 """Add necessary glue between all the supported SCM.
91
msb@chromium.orgd6504212010-01-13 17:34:31 +000092 This is the abstraction layer to bind to different SCM.
93 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000094 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
Edward Lemur231f5ea2018-01-31 19:02:36 +010095 out_cb=None, print_outbuf=False):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000096 self.url = url
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +000097 self._root_dir = root_dir
98 if self._root_dir:
99 self._root_dir = self._root_dir.replace('/', os.sep)
100 self.relpath = relpath
101 if self.relpath:
102 self.relpath = self.relpath.replace('/', os.sep)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000103 if self.relpath and self._root_dir:
104 self.checkout_path = os.path.join(self._root_dir, self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000105 if out_fh is None:
106 out_fh = sys.stdout
107 self.out_fh = out_fh
108 self.out_cb = out_cb
Edward Lemur231f5ea2018-01-31 19:02:36 +0100109 self.print_outbuf = print_outbuf
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000110
111 def Print(self, *args, **kwargs):
112 kwargs.setdefault('file', self.out_fh)
113 if kwargs.pop('timestamp', True):
114 self.out_fh.write('[%s] ' % gclient_utils.Elapsed())
115 print(*args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000116
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000117 def RunCommand(self, command, options, args, file_list=None):
agabledebf6c82016-12-21 12:50:12 -0800118 commands = ['update', 'updatesingle', 'revert',
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000119 'revinfo', 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000120
121 if not command in commands:
122 raise gclient_utils.Error('Unknown command %s' % command)
123
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000124 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000125 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000126 command, self.__class__.__name__))
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000127
128 return getattr(self, command)(options, args, file_list)
129
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000130 @staticmethod
131 def _get_first_remote_url(checkout_path):
132 log = scm.GIT.Capture(
133 ['config', '--local', '--get-regexp', r'remote.*.url'],
134 cwd=checkout_path)
135 # Get the second token of the first line of the log.
136 return log.splitlines()[0].split(' ', 1)[1]
137
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000138 def GetCacheMirror(self):
Robert Iannuccia19649b2018-06-29 16:31:45 +0000139 if getattr(self, 'cache_dir', None):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000140 url, _ = gclient_utils.SplitUrlRevision(self.url)
141 return git_cache.Mirror(url)
142 return None
143
smut@google.comd33eab32014-07-07 19:35:18 +0000144 def GetActualRemoteURL(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000145 """Attempt to determine the remote URL for this SCMWrapper."""
smut@google.comd33eab32014-07-07 19:35:18 +0000146 # Git
borenet@google.combda475e2014-03-24 19:04:45 +0000147 if os.path.exists(os.path.join(self.checkout_path, '.git')):
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000148 actual_remote_url = self._get_first_remote_url(self.checkout_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000149
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000150 mirror = self.GetCacheMirror()
151 # If the cache is used, obtain the actual remote URL from there.
152 if (mirror and mirror.exists() and
153 mirror.mirror_path.replace('\\', '/') ==
154 actual_remote_url.replace('\\', '/')):
155 actual_remote_url = self._get_first_remote_url(mirror.mirror_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000156 return actual_remote_url
borenet@google.com88d10082014-03-21 17:24:48 +0000157 return None
158
borenet@google.com4e9be262014-04-08 19:40:30 +0000159 def DoesRemoteURLMatch(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000160 """Determine whether the remote URL of this checkout is the expected URL."""
161 if not os.path.exists(self.checkout_path):
162 # A checkout which doesn't exist can't be broken.
163 return True
164
smut@google.comd33eab32014-07-07 19:35:18 +0000165 actual_remote_url = self.GetActualRemoteURL(options)
borenet@google.com88d10082014-03-21 17:24:48 +0000166 if actual_remote_url:
borenet@google.com8156c9f2014-04-01 16:41:36 +0000167 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
168 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000169
170 # This may occur if the self.checkout_path exists but does not contain a
171 # valid git checkout.
172 return False
borenet@google.com88d10082014-03-21 17:24:48 +0000173
borenet@google.comb09097a2014-04-09 19:09:08 +0000174 def _DeleteOrMove(self, force):
175 """Delete the checkout directory or move it out of the way.
176
177 Args:
178 force: bool; if True, delete the directory. Otherwise, just move it.
179 """
borenet@google.comb2256212014-05-07 20:57:28 +0000180 if force and os.environ.get('CHROME_HEADLESS') == '1':
181 self.Print('_____ Conflicting directory found in %s. Removing.'
182 % self.checkout_path)
183 gclient_utils.AddWarning('Conflicting directory %s deleted.'
184 % self.checkout_path)
185 gclient_utils.rmtree(self.checkout_path)
186 else:
187 bad_scm_dir = os.path.join(self._root_dir, '_bad_scm',
188 os.path.dirname(self.relpath))
189
190 try:
191 os.makedirs(bad_scm_dir)
192 except OSError as e:
193 if e.errno != errno.EEXIST:
194 raise
195
196 dest_path = tempfile.mkdtemp(
197 prefix=os.path.basename(self.relpath),
198 dir=bad_scm_dir)
199 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
200 % (self.checkout_path, dest_path))
201 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
202 % (self.checkout_path, dest_path))
203 shutil.move(self.checkout_path, dest_path)
borenet@google.comb09097a2014-04-09 19:09:08 +0000204
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000205
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000206class GitWrapper(SCMWrapper):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000207 """Wrapper for Git"""
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000208 name = 'git'
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000209 remote = 'origin'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000210
Robert Iannuccia19649b2018-06-29 16:31:45 +0000211 @property
212 def cache_dir(self):
213 try:
214 return git_cache.Mirror.GetCachePath()
215 except RuntimeError:
216 return None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000217
John Budorick0f7b2002018-01-19 15:46:17 -0800218 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000219 """Removes 'git+' fake prefix from git URL."""
Henrique Ferreiroe72279d2019-04-17 12:01:50 +0000220 if url and (url.startswith('git+http://') or
221 url.startswith('git+https://')):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000222 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800223 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000224 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
225 if self.out_cb:
226 filter_kwargs['predicate'] = self.out_cb
227 self.filter = gclient_utils.GitFilter(**filter_kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +0000228 self._running_under_rosetta = None
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000229
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000230 def GetCheckoutRoot(self):
231 return scm.GIT.GetCheckoutRoot(self.checkout_path)
232
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000233 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000234 """Returns the given revision's date in ISO-8601 format (which contains the
235 time zone)."""
236 # TODO(floitsch): get the time-stamp of the given revision and not just the
237 # time-stamp of the currently checked out revision.
238 return self._Capture(['log', '-n', '1', '--format=%ai'])
239
Aaron Gablef4068aa2017-12-12 15:14:09 -0800240 def _GetDiffFilenames(self, base):
241 """Returns the names of files modified since base."""
242 return self._Capture(
Raul Tambrecd862e32019-05-10 21:19:00 +0000243 # Filter to remove base if it is None.
244 list(filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only',
245 base])
246 )).split()
Aaron Gablef4068aa2017-12-12 15:14:09 -0800247
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000248 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 15:45:56 -0800249 _, revision = gclient_utils.SplitUrlRevision(self.url)
250 if not revision:
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000251 revision = 'refs/remotes/%s/main' % self.remote
Aaron Gable1853f662018-02-12 15:45:56 -0800252 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000253
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000254 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000255 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000256 repository.
257
258 The patch file is generated from a diff of the merge base of HEAD and
259 its upstream branch.
260 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700261 try:
262 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
263 except subprocess2.CalledProcessError:
264 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000265 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700266 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000267 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000268 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000269
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800270 def _Scrub(self, target, options):
271 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000272 quiet = []
273 if not options.verbose:
274 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800275 self._Run(['reset', '--hard', target] + quiet, options)
276 if options.force and options.delete_unversioned_trees:
277 # where `target` is a commit that contains both upper and lower case
278 # versions of the same file on a case insensitive filesystem, we are
279 # actually in a broken state here. The index will have both 'a' and 'A',
280 # but only one of them will exist on the disk. To progress, we delete
281 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800282 output = self._Capture([
283 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800284 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800285 # --porcelain (v1) looks like:
286 # XY filename
287 try:
288 filename = line[3:]
289 self.Print('_____ Deleting residual after reset: %r.' % filename)
290 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800291 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800292 except OSError:
293 pass
294
John Budorick882c91e2018-07-12 22:11:41 +0000295 def _FetchAndReset(self, revision, file_list, options):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800296 """Equivalent to git fetch; git reset."""
Edward Lemur579c9862018-07-13 23:17:51 +0000297 self._SetFetchConfig(options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000298
dnj@chromium.org680f2172014-06-25 00:39:32 +0000299 self._Fetch(options, prune=True, quiet=options.verbose)
John Budorick882c91e2018-07-12 22:11:41 +0000300 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000301 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800302 files = self._Capture(
303 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambreb946b232019-03-26 14:48:46 +0000304 file_list.extend(
Edward Lemur26a8b9f2019-08-15 20:46:44 +0000305 [os.path.join(self.checkout_path, f) for f in files])
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000306
szager@chromium.org8a139702014-06-20 15:55:01 +0000307 def _DisableHooks(self):
308 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
309 if not os.path.isdir(hook_dir):
310 return
311 for f in os.listdir(hook_dir):
312 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000313 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
314 if os.path.exists(disabled_hook_path):
315 os.remove(disabled_hook_path)
316 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000317
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000318 def _maybe_break_locks(self, options):
319 """This removes all .lock files from this repo's .git directory, if the
320 user passed the --break_repo_locks command line flag.
321
322 In particular, this will cleanup index.lock files, as well as ref lock
323 files.
324 """
325 if options.break_repo_locks:
326 git_dir = os.path.join(self.checkout_path, '.git')
327 for path, _, filenames in os.walk(git_dir):
328 for filename in filenames:
329 if filename.endswith('.lock'):
330 to_break = os.path.join(path, filename)
331 self.Print('breaking lock: %s' % (to_break,))
332 try:
333 os.remove(to_break)
334 except OSError as ex:
335 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
336 raise
337
Edward Lemur3acbc742019-05-30 17:57:35 +0000338 def apply_patch_ref(self, patch_repo, patch_rev, target_rev, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000339 file_list):
340 """Apply a patch on top of the revision we're synced at.
341
Edward Lemur3acbc742019-05-30 17:57:35 +0000342 The patch ref is given by |patch_repo|@|patch_rev|.
343 |target_rev| is usually the branch that the |patch_rev| was uploaded against
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000344 (e.g. 'refs/heads/main'), but this is not required.
Edward Lemur3acbc742019-05-30 17:57:35 +0000345
346 We cherry-pick all commits reachable from |patch_rev| on top of the curret
347 HEAD, excluding those reachable from |target_rev|
348 (i.e. git cherry-pick target_rev..patch_rev).
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000349
350 Graphically, it looks like this:
351
Edward Lemur3acbc742019-05-30 17:57:35 +0000352 ... -> o -> [possibly already landed commits] -> target_rev
353 \
354 -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000355
Edward Lemur3acbc742019-05-30 17:57:35 +0000356 The final checkout state is then:
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000357
Edward Lemur3acbc742019-05-30 17:57:35 +0000358 ... -> HEAD -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000359
360 After application, if |options.reset_patch_ref| is specified, we soft reset
Edward Lemur3acbc742019-05-30 17:57:35 +0000361 the cherry-picked changes, keeping them in git index only.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000362
363 Args:
Edward Lemur3acbc742019-05-30 17:57:35 +0000364 patch_repo: The patch origin.
365 e.g. 'https://foo.googlesource.com/bar'
366 patch_rev: The revision to patch.
367 e.g. 'refs/changes/1234/34/1'.
368 target_rev: The revision to use when finding the merge base.
369 Typically, the branch that the patch was uploaded against.
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000370 e.g. 'refs/heads/main' or 'refs/heads/infra/config'.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000371 options: The options passed to gclient.
372 file_list: A list where modified files will be appended.
373 """
374
Edward Lemurca7d8812018-07-24 17:42:45 +0000375 # Abort any cherry-picks in progress.
376 try:
377 self._Capture(['cherry-pick', '--abort'])
378 except subprocess2.CalledProcessError:
379 pass
380
Edward Lesmesc621b212018-03-21 20:26:56 -0400381 base_rev = self._Capture(['rev-parse', 'HEAD'])
Edward Lemura0ffbe42019-05-01 16:52:18 +0000382
Edward Lemur3acbc742019-05-30 17:57:35 +0000383 if not target_rev:
Edward Lemur4c5c8ab2019-06-07 15:58:13 +0000384 raise gclient_utils.Error('A target revision for the patch must be given')
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000385
386 if target_rev.startswith(('refs/heads/', 'refs/branch-heads')):
Edward Lesmesf627d9f2020-07-23 19:50:50 +0000387 # If |target_rev| is in refs/heads/** or refs/branch-heads/**, try first
388 # to find the corresponding remote ref for it, since |target_rev| might
389 # point to a local ref which is not up to date with the corresponding
390 # remote ref.
Edward Lemur3acbc742019-05-30 17:57:35 +0000391 remote_ref = ''.join(scm.GIT.RefToRemoteRef(target_rev, self.remote))
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000392 self.Print('Trying the corresponding remote ref for %r: %r\n' % (
Edward Lemur3acbc742019-05-30 17:57:35 +0000393 target_rev, remote_ref))
394 if scm.GIT.IsValidRevision(self.checkout_path, remote_ref):
Josip Sokcevic3d7cbce2021-10-05 20:48:04 +0000395 # refs/remotes may need to be updated to cleanly cherry-pick changes.
396 # See https://crbug.com/1255178.
397 self._Capture(['fetch', '--no-tags', self.remote, target_rev])
Edward Lemur3acbc742019-05-30 17:57:35 +0000398 target_rev = remote_ref
399 elif not scm.GIT.IsValidRevision(self.checkout_path, target_rev):
400 # Fetch |target_rev| if it's not already available.
401 url, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmes07a68342021-04-20 23:39:30 +0000402 mirror = self._GetMirror(url, options, target_rev, target_rev)
Edward Lemur3acbc742019-05-30 17:57:35 +0000403 if mirror:
404 rev_type = 'branch' if target_rev.startswith('refs/') else 'hash'
405 self._UpdateMirrorIfNotContains(mirror, options, rev_type, target_rev)
406 self._Fetch(options, refspec=target_rev)
Edward Lemura0ffbe42019-05-01 16:52:18 +0000407
Ravi Mistryecda7822022-02-28 16:22:20 +0000408 patch_revs_to_process = [patch_rev]
409
410 if hasattr(options, 'download_topics') and options.download_topics:
411 # We will now:
412 # 1. Find the topic of the Gerrit change specified in the patch_rev.
413 # 2. Find all changes with that topic.
414 # 3. Append patch_rev of the changes with the same topic to the patch_revs
415 # to process.
416
417 # Parse the patch_Rev to extract the CL and patchset.
418 patch_rev_tokens = patch_rev.split('/')
419 change = patch_rev_tokens[-2]
420 # Parse the gerrit host out of self.url.
421 host = self.url.split(os.path.sep)[-1].rstrip('.git')
422 gerrit_host_url = '%s-review.googlesource.com' % host
423
424 # 1. Find the topic of the Gerrit change specified in the patch_rev.
425 change_object = gerrit_util.GetChange(gerrit_host_url, change)
426 topic = change_object.get('topic')
427 if topic:
428 # 2. Find all changes with that topic.
429 changes_with_same_topic = gerrit_util.QueryChanges(
430 gerrit_host_url,
431 [('topic', topic), ('status', 'open'), ('repo', host)],
432 o_params=['ALL_REVISIONS'])
433 for c in changes_with_same_topic:
434 if str(c['_number']) == change:
435 # This change is already in the patch_rev.
436 continue
437 self.Print('Found CL %d with the topic name %s' % (
438 c['_number'], topic))
439 # 3. Append patch_rev of the changes with the same topic to the
440 # patch_revs to process.
441 curr_rev = c['current_revision']
442 new_patch_rev = c['revisions'][curr_rev]['ref']
443 patch_revs_to_process.append(new_patch_rev)
444
Edward Lesmesc621b212018-03-21 20:26:56 -0400445 self._Capture(['reset', '--hard'])
Ravi Mistryecda7822022-02-28 16:22:20 +0000446 for pr in patch_revs_to_process:
447 self.Print('===Applying patch===')
448 self.Print('Revision to patch is %r @ %r.' % (patch_repo, pr))
449 self.Print('Current dir is %r' % self.checkout_path)
450 self._Capture(['fetch', '--no-tags', patch_repo, pr])
451 pr = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400452
Ravi Mistryecda7822022-02-28 16:22:20 +0000453 if not options.rebase_patch_ref:
454 self._Capture(['checkout', pr])
455 # Adjust base_rev to be the first parent of our checked out patch ref;
456 # This will allow us to correctly extend `file_list`, and will show the
457 # correct file-list to programs which do `git diff --cached` expecting
458 # to see the patch diff.
459 base_rev = self._Capture(['rev-parse', pr+'~'])
460 else:
461 self.Print('Will cherrypick %r .. %r on top of %r.' % (
462 target_rev, pr, base_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000463 try:
Ravi Mistryecda7822022-02-28 16:22:20 +0000464 if scm.GIT.IsAncestor(self.checkout_path, pr, target_rev):
465 if len(patch_revs_to_process) > 1:
466 # If there are multiple patch_revs_to_process then we do not want
467 # want to invalidate a previous patch so throw an error.
468 raise gclient_utils.Error(
469 'patch_rev %s is an ancestor of target_rev %s. This '
470 'situation is unsupported when we need to apply multiple '
471 'patch_revs: %s' % (pr, target_rev, patch_revs_to_process))
472 # If |patch_rev| is an ancestor of |target_rev|, check it out.
473 self._Capture(['checkout', pr])
474 else:
475 # If a change was uploaded on top of another change, which has
476 # already landed, one of the commits in the cherry-pick range will
477 # be redundant, since it has already landed and its changes
478 # incorporated in the tree.
479 # We pass '--keep-redundant-commits' to ignore those changes.
480 self._Capture(['cherry-pick', target_rev + '..' + pr,
481 '--keep-redundant-commits'])
Edward Lemurca7d8812018-07-24 17:42:45 +0000482
Ravi Mistryecda7822022-02-28 16:22:20 +0000483 except subprocess2.CalledProcessError as e:
484 self.Print('Failed to apply patch.')
485 self.Print('Revision to patch was %r @ %r.' % (patch_repo, pr))
486 self.Print('Tried to cherrypick %r .. %r on top of %r.' % (
487 target_rev, pr, base_rev))
488 self.Print('Current dir is %r' % self.checkout_path)
489 self.Print('git returned non-zero exit status %s:\n%s' % (
490 e.returncode, e.stderr.decode('utf-8')))
491 # Print the current status so that developers know what changes caused
492 # the patch failure, since git cherry-pick doesn't show that
493 # information.
494 self.Print(self._Capture(['status']))
495 try:
496 self._Capture(['cherry-pick', '--abort'])
497 except subprocess2.CalledProcessError:
498 pass
499 raise
500
501 if file_list is not None:
502 file_list.extend(self._GetDiffFilenames(base_rev))
Edward Lemurca7d8812018-07-24 17:42:45 +0000503
Edward Lesmesc621b212018-03-21 20:26:56 -0400504 if options.reset_patch_ref:
505 self._Capture(['reset', '--soft', base_rev])
506
msb@chromium.orge28e4982009-09-25 20:51:45 +0000507 def update(self, options, args, file_list):
508 """Runs git to update or transparently checkout the working copy.
509
510 All updated files will be appended to file_list.
511
512 Raises:
513 Error: if can't get URL for relative path.
514 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000515 if args:
516 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
517
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000518 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000519
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000520 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000521 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000522 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000523 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000524 # Override the revision number.
525 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000526 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000527 # Check again for a revision in case an initial ref was specified
528 # in the url, for example bla.git@refs/heads/custombranch
529 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000530 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000531 if not revision:
Josip Sokcevic091f5ac2021-01-14 23:14:21 +0000532 # If a dependency is not pinned, track the default remote branch.
533 revision = scm.GIT.GetRemoteHeadRef(self.checkout_path, self.url,
534 self.remote)
Edward Lesmes4ea67bb2021-04-20 17:33:52 +0000535 if revision.startswith('origin/'):
536 revision = 'refs/remotes/' + revision
msb@chromium.orge28e4982009-09-25 20:51:45 +0000537
Tomasz Wiszkowskid4e66882021-08-19 21:35:09 +0000538 if managed and platform.system() == 'Windows':
szager@chromium.org8a139702014-06-20 15:55:01 +0000539 self._DisableHooks()
540
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000541 printed_path = False
542 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000543 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700544 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000545 verbose = ['--verbose']
546 printed_path = True
547
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000548 revision_ref = revision
549 if ':' in revision:
550 revision_ref, _, revision = revision.partition(':')
551
Edward Lesmes8073a502020-04-15 02:11:14 +0000552 if revision_ref.startswith('refs/branch-heads'):
553 options.with_branch_heads = True
554
Edward Lesmes07a68342021-04-20 23:39:30 +0000555 mirror = self._GetMirror(url, options, revision, revision_ref)
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000556 if mirror:
557 url = mirror.mirror_path
Edward Lemurdb5c5ad2019-03-07 16:00:24 +0000558
John Budorick882c91e2018-07-12 22:11:41 +0000559 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
560 if remote_ref:
561 # Rewrite remote refs to their local equivalents.
562 revision = ''.join(remote_ref)
563 rev_type = "branch"
564 elif revision.startswith('refs/'):
565 # Local branch? We probably don't want to support, since DEPS should
566 # always specify branches as they are in the upstream repo.
567 rev_type = "branch"
568 else:
569 # hash is also a tag, only make a distinction at checkout
570 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000571
primiano@chromium.org1c127382015-02-17 11:15:40 +0000572 # If we are going to introduce a new project, there is a possibility that
573 # we are syncing back to a state where the project was originally a
574 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
575 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
576 # In such case, we might have a backup of the former .git folder, which can
577 # be used to avoid re-fetching the entire repo again (useful for bisects).
578 backup_dir = self.GetGitBackupDirPath()
579 target_dir = os.path.join(self.checkout_path, '.git')
580 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
581 gclient_utils.safe_makedirs(self.checkout_path)
582 os.rename(backup_dir, target_dir)
583 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800584 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000585
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000586 if (not os.path.exists(self.checkout_path) or
587 (os.path.isdir(self.checkout_path) and
588 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000589 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000590 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000591 try:
John Budorick882c91e2018-07-12 22:11:41 +0000592 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000593 except subprocess2.CalledProcessError:
594 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000595 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000596 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800597 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20 +0000598 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambrec2f74c12019-03-19 05:55:53 +0000599 file_list.extend(
Edward Lemur979fa782019-08-13 22:44:05 +0000600 [os.path.join(self.checkout_path, f) for f in files])
John Budorick21a51b32018-09-19 19:39:20 +0000601 if mirror:
602 self._Capture(
603 ['remote', 'set-url', '--push', 'origin', mirror.url])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000604 if not verbose:
605 # Make the output a little prettier. It's nice to have some whitespace
606 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000607 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000608 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000609
John Budorick21a51b32018-09-19 19:39:20 +0000610 if mirror:
611 self._Capture(
612 ['remote', 'set-url', '--push', 'origin', mirror.url])
613
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000614 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000615 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000616 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
617 return self._Capture(['rev-parse', '--verify', 'HEAD'])
618
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000619 self._maybe_break_locks(options)
620
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000621 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000622 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000623
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000624 # See if the url has changed (the unittests use git://foo for the url, let
625 # that through).
626 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
627 return_early = False
628 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
629 # unit test pass. (and update the comment above)
630 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
631 # This allows devs to use experimental repos which have a different url
632 # but whose branch(s) are the same as official repos.
Raul Tambrecd862e32019-05-10 21:19:00 +0000633 if (current_url.rstrip('/') != url.rstrip('/') and url != 'git://foo' and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000634 subprocess2.capture(
Aaron Gableac9b0f32019-04-18 17:38:37 +0000635 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000636 cwd=self.checkout_path).strip() != 'False'):
Anthony Polito486f1812020-08-04 23:40:33 +0000637 self.Print('_____ switching %s from %s to new upstream %s' % (
638 self.relpath, current_url, url))
iannucci@chromium.org78514212014-08-20 23:08:00 +0000639 if not (options.force or options.reset):
640 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700641 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000642 # Switch over to the new upstream
643 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000644 if mirror:
645 with open(os.path.join(
646 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
647 'w') as fh:
648 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000649 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
650 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000651
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000652 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000653 else:
John Budorick882c91e2018-07-12 22:11:41 +0000654 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000655
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000656 if return_early:
657 return self._Capture(['rev-parse', '--verify', 'HEAD'])
658
msb@chromium.org5bde4852009-12-14 16:47:12 +0000659 cur_branch = self._GetCurrentBranch()
660
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000661 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000662 # 0) HEAD is detached. Probably from our initial clone.
663 # - make sure HEAD is contained by a named ref, then update.
664 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700665 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000666 # - try to rebase onto the new hash or branch
667 # 2) current branch is tracking a remote branch with local committed
668 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000669 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000670 # 3) current branch is tracking a remote branch w/or w/out changes, and
671 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000672 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000673 # 4) current branch is tracking a remote branch, but DEPS switches to a
674 # different remote branch, and
675 # a) current branch has no local changes, and --force:
676 # - checkout new branch
677 # b) current branch has local changes, and --force and --reset:
678 # - checkout new branch
679 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000680
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000681 # GetUpstreamBranch returns something like 'refs/remotes/origin/main' for
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000682 # a tracking branch
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000683 # or 'main' if not a tracking branch (it's based on a specific rev/hash)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000684 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000685 if cur_branch is None:
686 upstream_branch = None
687 current_type = "detached"
688 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000689 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000690 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
691 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
692 current_type = "hash"
693 logging.debug("Current branch is not tracking an upstream (remote)"
694 " branch.")
695 elif upstream_branch.startswith('refs/remotes'):
696 current_type = "branch"
697 else:
698 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000699
Edward Lemur579c9862018-07-13 23:17:51 +0000700 self._SetFetchConfig(options)
Edward Lemur579c9862018-07-13 23:17:51 +0000701
Michael Spang73fac912019-03-08 18:44:19 +0000702 # Fetch upstream if we don't already have |revision|.
John Budorick882c91e2018-07-12 22:11:41 +0000703 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
Michael Spang73fac912019-03-08 18:44:19 +0000704 self._Fetch(options, prune=options.force)
705
706 if not scm.GIT.IsValidRevision(self.checkout_path, revision,
707 sha_only=True):
708 # Update the remotes first so we have all the refs.
709 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
710 cwd=self.checkout_path)
711 if verbose:
712 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000713
John Budorick882c91e2018-07-12 22:11:41 +0000714 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200715
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000716 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000717 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000718 target = 'HEAD'
719 if options.upstream and upstream_branch:
720 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800721 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000722
msb@chromium.org786fb682010-06-02 15:16:23 +0000723 if current_type == 'detached':
724 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800725 # We just did a Scrub, this is as clean as it's going to get. In
726 # particular if HEAD is a commit that contains two versions of the same
727 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
728 # to actually "Clean" the checkout; that commit is uncheckoutable on this
729 # system. The best we can do is carry forward to the checkout step.
730 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000731 self._CheckClean(revision)
732 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000733 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000734 self.Print('Up-to-date; skipping checkout.')
735 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000736 # 'git checkout' may need to overwrite existing untracked files. Allow
737 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000738 self._Checkout(
739 options,
John Budorick882c91e2018-07-12 22:11:41 +0000740 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000741 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000742 quiet=True,
743 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000744 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000745 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000746 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000747 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700748 # Can't find a merge-base since we don't know our upstream. That makes
749 # this command VERY likely to produce a rebase failure. For now we
750 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000751 upstream_branch = self.remote
752 if options.revision or deps_revision:
753 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700754 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700755 printed_path=printed_path, merge=options.merge)
756 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000757 elif rev_type == 'hash':
758 # case 2
759 self._AttemptRebase(upstream_branch, file_list, options,
760 newbase=revision, printed_path=printed_path,
761 merge=options.merge)
762 printed_path = True
763 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000764 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000765 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000766 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000767 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000768 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000769 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000770 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000771 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
772 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000773 force_switch = False
774 if options.force:
775 try:
John Budorick882c91e2018-07-12 22:11:41 +0000776 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000777 # case 4a
778 force_switch = True
779 except gclient_utils.Error as e:
780 if options.reset:
781 # case 4b
782 force_switch = True
783 else:
784 switch_error = '%s\n%s' % (e.message, switch_error)
785 if force_switch:
786 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000787 (upstream_branch, new_base))
788 switch_branch = 'gclient_' + remote_ref[1]
789 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000790 self._Checkout(options, switch_branch, force=True, quiet=True)
791 else:
792 # case 4c
793 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000794 else:
795 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800796 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000797 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000798 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000799 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000800 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000801 if options.merge:
802 merge_args.append('--ff')
803 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000804 merge_args.append('--ff-only')
805 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000806 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000807 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700808 rebase_files = []
Edward Lemur979fa782019-08-13 22:44:05 +0000809 if re.match(b'fatal: Not possible to fast-forward, aborting.',
810 e.stderr):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000811 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000812 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700813 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000814 printed_path = True
815 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000816 if not options.auto_rebase:
817 try:
818 action = self._AskForData(
819 'Cannot %s, attempt to rebase? '
820 '(y)es / (q)uit / (s)kip : ' %
821 ('merge' if options.merge else 'fast-forward merge'),
822 options)
823 except ValueError:
824 raise gclient_utils.Error('Invalid Character')
825 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700826 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000827 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000828 printed_path = True
829 break
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000830
831 if re.match(r'quit|q', action, re.I):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000832 raise gclient_utils.Error("Can't fast-forward, please merge or "
833 "rebase manually.\n"
834 "cd %s && git " % self.checkout_path
835 + "rebase %s" % upstream_branch)
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000836
837 if re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000838 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000839 return
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000840
841 self.Print('Input not recognized')
Edward Lemur979fa782019-08-13 22:44:05 +0000842 elif re.match(b"error: Your local changes to '.*' would be "
843 b"overwritten by merge. Aborting.\nPlease, commit your "
844 b"changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000845 e.stderr):
846 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000847 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700848 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000849 printed_path = True
Edward Lemur979fa782019-08-13 22:44:05 +0000850 raise gclient_utils.Error(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000851 else:
852 # Some other problem happened with the merge
853 logging.error("Error during fast-forward merge in %s!" % self.relpath)
Edward Lemur979fa782019-08-13 22:44:05 +0000854 self.Print(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000855 raise
856 else:
857 # Fast-forward merge was successful
858 if not re.match('Already up-to-date.', merge_output) or verbose:
859 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700860 self.Print('_____ %s at %s' % (self.relpath, revision),
861 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000862 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000863 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000864 if not verbose:
865 # Make the output a little prettier. It's nice to have some
866 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000867 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000868
agablec3937b92016-10-25 10:13:03 -0700869 if file_list is not None:
870 file_list.extend(
871 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000872
873 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000874 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700875 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000876 '\nConflict while rebasing this branch.\n'
877 'Fix the conflict and run gclient again.\n'
878 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700879 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000880
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000881 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000882 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
883 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000884
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000885 # If --reset and --delete_unversioned_trees are specified, remove any
886 # untracked directories.
887 if options.reset and options.delete_unversioned_trees:
888 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
889 # merge-base by default), so doesn't include untracked files. So we use
890 # 'git ls-files --directory --others --exclude-standard' here directly.
891 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800892 ['-c', 'core.quotePath=false', 'ls-files',
893 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000894 self.checkout_path)
895 for path in (p for p in paths.splitlines() if p.endswith('/')):
896 full_path = os.path.join(self.checkout_path, path)
897 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000898 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000899 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000900
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000901 return self._Capture(['rev-parse', '--verify', 'HEAD'])
902
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000903 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000904 """Reverts local modifications.
905
906 All reverted files will be appended to file_list.
907 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000908 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000909 # revert won't work if the directory doesn't exist. It needs to
910 # checkout instead.
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000911 self.Print('_____ %s is missing, syncing instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000912 # Don't reuse the args.
913 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000914
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000915 default_rev = "refs/heads/main"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000916 if options.upstream:
917 if self._GetCurrentBranch():
918 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
919 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000920 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000921 if not deps_revision:
922 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000923 if deps_revision.startswith('refs/heads/'):
924 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700925 try:
926 deps_revision = self.GetUsableRev(deps_revision, options)
927 except NoUsableRevError as e:
928 # If the DEPS entry's url and hash changed, try to update the origin.
929 # See also http://crbug.com/520067.
John Budorickd94f8ea2020-03-27 15:55:24 +0000930 logging.warning(
931 "Couldn't find usable revision, will retrying to update instead: %s",
smutae7ea312016-07-18 11:59:41 -0700932 e.message)
933 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000934
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000935 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800936 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000937
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800938 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000939 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000940
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000941 if file_list is not None:
942 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
943
944 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000945 """Returns revision"""
946 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000947
msb@chromium.orge28e4982009-09-25 20:51:45 +0000948 def runhooks(self, options, args, file_list):
949 self.status(options, args, file_list)
950
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000951 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000952 """Display status information."""
953 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000954 self.Print('________ couldn\'t run status in %s:\n'
955 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000956 else:
Anthony Politobb457342019-11-15 22:26:01 +0000957 merge_base = []
958 if self.url:
959 _, base_rev = gclient_utils.SplitUrlRevision(self.url)
960 if base_rev:
961 merge_base = [base_rev]
Aaron Gablef4068aa2017-12-12 15:14:09 -0800962 self._Run(
963 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
Edward Lemur24146be2019-08-01 21:44:52 +0000964 options, always_show_header=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000965 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800966 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000967 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000968
smutae7ea312016-07-18 11:59:41 -0700969 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700970 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700971 sha1 = None
972 if not os.path.isdir(self.checkout_path):
973 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800974 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700975
976 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
977 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700978 else:
agable41e3a6c2016-10-20 11:36:56 -0700979 # May exist in origin, but we don't have it yet, so fetch and look
980 # again.
981 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700982 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
983 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700984
985 if not sha1:
986 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800987 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700988
989 return sha1
990
primiano@chromium.org1c127382015-02-17 11:15:40 +0000991 def GetGitBackupDirPath(self):
992 """Returns the path where the .git folder for the current project can be
993 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
994 return os.path.join(self._root_dir,
995 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
996
Edward Lesmes07a68342021-04-20 23:39:30 +0000997 def _GetMirror(self, url, options, revision=None, revision_ref=None):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000998 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000999 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +00001000 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +00001001 mirror_kwargs = {
1002 'print_func': self.filter,
Edward Lesmes07a68342021-04-20 23:39:30 +00001003 'refs': [],
1004 'commits': [],
hinoka@google.comb1b54572014-04-16 22:29:23 +00001005 }
hinoka@google.comb1b54572014-04-16 22:29:23 +00001006 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
1007 mirror_kwargs['refs'].append('refs/branch-heads/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +00001008 elif revision_ref and revision_ref.startswith('refs/branch-heads/'):
1009 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001010 if hasattr(options, 'with_tags') and options.with_tags:
1011 mirror_kwargs['refs'].append('refs/tags/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +00001012 elif revision_ref and revision_ref.startswith('refs/tags/'):
1013 mirror_kwargs['refs'].append(revision_ref)
Edward Lesmes07a68342021-04-20 23:39:30 +00001014 if revision and not revision.startswith('refs/'):
1015 mirror_kwargs['commits'].append(revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +00001016 return git_cache.Mirror(url, **mirror_kwargs)
1017
John Budorick882c91e2018-07-12 22:11:41 +00001018 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -08001019 """Update a git mirror by fetching the latest commits from the remote,
1020 unless mirror already contains revision whose type is sha1 hash.
1021 """
John Budorick882c91e2018-07-12 22:11:41 +00001022 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -08001023 if options.verbose:
1024 self.Print('skipping mirror update, it has rev=%s already' % revision,
1025 timestamp=False)
1026 return
1027
szager@chromium.org3ec84f62014-08-22 21:00:22 +00001028 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001029 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +00001030 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001031 depth = 10
1032 else:
1033 depth = 10000
1034 else:
1035 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00001036 mirror.populate(verbose=options.verbose,
1037 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +00001038 depth=depth,
Vadim Shtayura08049e22017-10-11 00:14:52 +00001039 lock_timeout=getattr(options, 'lock_timeout', 0))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001040
John Budorick882c91e2018-07-12 22:11:41 +00001041 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001042 """Clone a git repository from the given URL.
1043
msb@chromium.org786fb682010-06-02 15:16:23 +00001044 Once we've cloned the repo, we checkout a working branch if the specified
1045 revision is a branch head. If it is a tag or a specific commit, then we
1046 leave HEAD detached as it makes future updates simpler -- in this case the
1047 user should first create a new branch or switch to an existing branch before
1048 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001049 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001050 # git clone doesn't seem to insert a newline properly before printing
1051 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001052 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +00001053 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +00001054 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001055 if self.cache_dir:
1056 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001057 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001058 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001059 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +00001060 # If the parent directory does not exist, Git clone on Windows will not
1061 # create it, so we need to do it manually.
1062 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001063 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001064
1065 template_dir = None
1066 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +00001067 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001068 # In the case of a subproject, the pinned sha is not necessarily the
1069 # head of the remote branch (so we can't just use --depth=N). Instead,
1070 # we tell git to fetch all the remote objects from SHA..HEAD by means of
1071 # a template git dir which has a 'shallow' file pointing to the sha.
1072 template_dir = tempfile.mkdtemp(
1073 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
1074 dir=parent_dir)
1075 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
1076 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
1077 template_file.write(revision)
1078 clone_cmd.append('--template=' + template_dir)
1079 else:
1080 # Otherwise, we're just interested in the HEAD. Just use --depth.
1081 clone_cmd.append('--depth=1')
1082
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001083 tmp_dir = tempfile.mkdtemp(
1084 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
1085 dir=parent_dir)
1086 try:
1087 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001088 if self.print_outbuf:
1089 print_stdout = True
Edward Lemur24146be2019-08-01 21:44:52 +00001090 filter_fn = None
Edward Lemur231f5ea2018-01-31 19:02:36 +01001091 else:
1092 print_stdout = False
Edward Lemur24146be2019-08-01 21:44:52 +00001093 filter_fn = self.filter
Edward Lemur231f5ea2018-01-31 19:02:36 +01001094 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
Edward Lemur24146be2019-08-01 21:44:52 +00001095 print_stdout=print_stdout, filter_fn=filter_fn)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001096 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +00001097 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
1098 os.path.join(self.checkout_path, '.git'))
Edward Lesmesd4e20f22020-07-15 21:11:08 +00001099 # TODO(https://github.com/git-for-windows/git/issues/2569): Remove once
1100 # fixed.
1101 if sys.platform.startswith('win'):
1102 try:
1103 self._Run(['config', '--unset', 'core.worktree'], options,
1104 cwd=self.checkout_path)
1105 except subprocess2.CalledProcessError:
1106 pass
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001107 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001108 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001109 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001110 finally:
1111 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001112 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001113 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001114 if template_dir:
1115 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +00001116 self._SetFetchConfig(options)
1117 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001118 revision = self._AutoFetchRef(options, revision)
1119 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1120 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +00001121 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +00001122 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001123 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +00001124 ('Checked out %s to a detached HEAD. Before making any commits\n'
1125 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1126 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +00001127 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001128
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001129 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001130 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001131 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001132 raise gclient_utils.Error("Background task requires input. Rerun "
1133 "gclient with --jobs=1 so that\n"
1134 "interaction is possible.")
Edward Lesmesae3586b2020-03-23 21:21:14 +00001135 return gclient_utils.AskForData(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001136
1137
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001138 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001139 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001140 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001141 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001142 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001143 revision = upstream
1144 if newbase:
1145 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001146 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001147 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001148 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001149 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001150 printed_path = True
1151 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001152 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001153
1154 if merge:
1155 merge_output = self._Capture(['merge', revision])
1156 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001157 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001158 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001159
1160 # Build the rebase command here using the args
1161 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1162 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001163 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001164 rebase_cmd.append('--verbose')
1165 if newbase:
1166 rebase_cmd.extend(['--onto', newbase])
1167 rebase_cmd.append(upstream)
1168 if branch:
1169 rebase_cmd.append(branch)
1170
1171 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001172 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
Raul Tambreb946b232019-03-26 14:48:46 +00001173 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001174 if (re.match(br'cannot rebase: you have unstaged changes', e.stderr) or
1175 re.match(br'cannot rebase: your index contains uncommitted changes',
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001176 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001177 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001178 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001179 'Cannot rebase because of unstaged changes.\n'
1180 '\'git reset --hard HEAD\' ?\n'
1181 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001182 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001183 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001184 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001185 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001186 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001187 break
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00001188
1189 if re.match(r'quit|q', rebase_action, re.I):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001190 raise gclient_utils.Error("Please merge or rebase manually\n"
1191 "cd %s && git " % self.checkout_path
1192 + "%s" % ' '.join(rebase_cmd))
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00001193
1194 if re.match(r'show|s', rebase_action, re.I):
Edward Lemur979fa782019-08-13 22:44:05 +00001195 self.Print('%s' % e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001196 continue
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00001197
1198 gclient_utils.Error("Input not recognized")
1199 continue
Edward Lemur979fa782019-08-13 22:44:05 +00001200 elif re.search(br'^CONFLICT', e.stdout, re.M):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001201 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1202 "Fix the conflict and run gclient again.\n"
1203 "See 'man git-rebase' for details.\n")
1204 else:
Edward Lemur979fa782019-08-13 22:44:05 +00001205 self.Print(e.stdout.decode('utf-8').strip())
1206 self.Print('Rebase produced error output:\n%s' %
1207 e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001208 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1209 "manually.\ncd %s && git " %
1210 self.checkout_path
1211 + "%s" % ' '.join(rebase_cmd))
1212
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001213 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001214 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001215 # Make the output a little prettier. It's nice to have some
1216 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001217 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001218
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001219 @staticmethod
1220 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001221 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1222 if not ok:
1223 raise gclient_utils.Error('git version %s < minimum required %s' %
1224 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001225
John Budorick882c91e2018-07-12 22:11:41 +00001226 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001227 # Special case handling if all 3 conditions are met:
1228 # * the mirros have recently changed, but deps destination remains same,
1229 # * the git histories of mirrors are conflicting.
1230 # * git cache is used
1231 # This manifests itself in current checkout having invalid HEAD commit on
1232 # most git operations. Since git cache is used, just deleted the .git
1233 # folder, and re-create it by cloning.
1234 try:
1235 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1236 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001237 if (b'fatal: bad object HEAD' in e.stderr
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001238 and self.cache_dir and self.cache_dir in url):
1239 self.Print((
1240 'Likely due to DEPS change with git cache_dir, '
1241 'the current commit points to no longer existing object.\n'
1242 '%s' % e)
1243 )
1244 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001245 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001246 else:
1247 raise
1248
msb@chromium.org786fb682010-06-02 15:16:23 +00001249 def _IsRebasing(self):
1250 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1251 # have a plumbing command to determine whether a rebase is in progress, so
1252 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1253 g = os.path.join(self.checkout_path, '.git')
1254 return (
1255 os.path.isdir(os.path.join(g, "rebase-merge")) or
1256 os.path.isdir(os.path.join(g, "rebase-apply")))
1257
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001258 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001259 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1260 if os.path.exists(lockfile):
1261 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001262 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001263 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1264 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001265 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001266
msb@chromium.org786fb682010-06-02 15:16:23 +00001267 # Make sure the tree is clean; see git-rebase.sh for reference
1268 try:
1269 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001270 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001271 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001272 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001273 '\tYou have unstaged changes.\n'
1274 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001275 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001276 try:
1277 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001278 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001279 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001280 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001281 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001282 '\tYour index contains uncommitted changes\n'
1283 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001284 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001285
agable83faed02016-10-24 14:37:10 -07001286 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001287 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1288 # reference by a commit). If not, error out -- most likely a rebase is
1289 # in progress, try to detect so we can give a better error.
1290 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001291 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1292 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001293 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001294 # Commit is not contained by any rev. See if the user is rebasing:
1295 if self._IsRebasing():
1296 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001297 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001298 '\tAlready in a conflict, i.e. (no branch).\n'
1299 '\tFix the conflict and run gclient again.\n'
1300 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1301 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001302 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001303 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001304 name = ('saved-by-gclient-' +
1305 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001306 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001307 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001308 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001309
msb@chromium.org5bde4852009-12-14 16:47:12 +00001310 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001311 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001312 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001313 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001314 return None
1315 return branch
1316
borenet@google.comc3e09d22014-04-10 13:58:18 +00001317 def _Capture(self, args, **kwargs):
Mike Frysinger286fb162019-09-30 03:14:10 +00001318 set_git_dir = 'cwd' not in kwargs
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001319 kwargs.setdefault('cwd', self.checkout_path)
1320 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001321 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001322 env = scm.GIT.ApplyEnvVars(kwargs)
Mike Frysinger286fb162019-09-30 03:14:10 +00001323 # If an explicit cwd isn't set, then default to the .git/ subdir so we get
1324 # stricter behavior. This can be useful in cases of slight corruption --
1325 # we don't accidentally go corrupting parent git checks too. See
1326 # https://crbug.com/1000825 for an example.
1327 if set_git_dir:
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001328 git_dir = os.path.abspath(os.path.join(self.checkout_path, '.git'))
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001329 # Depending on how the .gclient file was defined, self.checkout_path
1330 # might be set to a unicode string, not a regular string; on Windows
1331 # Python2, we can't set env vars to be unicode strings, so we
1332 # forcibly cast the value to a string before setting it.
1333 env.setdefault('GIT_DIR', str(git_dir))
Raul Tambrecd862e32019-05-10 21:19:00 +00001334 ret = subprocess2.check_output(
1335 ['git'] + args, env=env, **kwargs).decode('utf-8')
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001336 if strip:
1337 ret = ret.strip()
Erik Chene16ffff2019-10-14 20:35:53 +00001338 self.Print('Finished running: %s %s' % ('git', ' '.join(args)))
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001339 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001340
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001341 def _Checkout(self, options, ref, force=False, quiet=None):
1342 """Performs a 'git-checkout' operation.
1343
1344 Args:
1345 options: The configured option set
1346 ref: (str) The branch/commit to checkout
Quinten Yearsley925cedb2020-04-13 17:49:39 +00001347 quiet: (bool/None) Whether or not the checkout should pass '--quiet'; if
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001348 'None', the behavior is inferred from 'options.verbose'.
1349 Returns: (str) The output of the checkout operation
1350 """
1351 if quiet is None:
1352 quiet = (not options.verbose)
1353 checkout_args = ['checkout']
1354 if force:
1355 checkout_args.append('--force')
1356 if quiet:
1357 checkout_args.append('--quiet')
1358 checkout_args.append(ref)
1359 return self._Capture(checkout_args)
1360
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001361 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1362 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001363 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemur9a5e3bd2019-04-02 23:37:45 +00001364 # When updating, the ref is modified to be a remote ref .
1365 # (e.g. refs/heads/NAME becomes refs/remotes/REMOTE/NAME).
1366 # Try to reverse that mapping.
1367 original_ref = scm.GIT.RemoteRefToRef(refspec, self.remote)
1368 if original_ref:
1369 refspec = original_ref + ':' + refspec
1370 # When a mirror is configured, it only fetches
1371 # refs/{heads,branch-heads,tags}/*.
1372 # If asked to fetch other refs, we must fetch those directly from the
1373 # repository, and not from the mirror.
1374 if not original_ref.startswith(
1375 ('refs/heads/', 'refs/branch-heads/', 'refs/tags/')):
1376 remote, _ = gclient_utils.SplitUrlRevision(self.url)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001377 fetch_cmd = cfg + [
1378 'fetch',
1379 remote or self.remote,
1380 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001381 if refspec:
1382 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001383
1384 if prune:
1385 fetch_cmd.append('--prune')
1386 if options.verbose:
1387 fetch_cmd.append('--verbose')
danakjd5c0b562019-11-08 17:27:47 +00001388 if not hasattr(options, 'with_tags') or not options.with_tags:
1389 fetch_cmd.append('--no-tags')
dnj@chromium.org680f2172014-06-25 00:39:32 +00001390 elif quiet:
1391 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001392 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001393
Edward Lemur579c9862018-07-13 23:17:51 +00001394 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001395 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1396 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001397 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001398 try:
1399 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1400 options)
1401 self._Run(['config', 'remote.%s.fetch' % self.remote,
1402 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1403 except subprocess2.CalledProcessError as e:
1404 # If exit code was 5, it means we attempted to unset a config that
1405 # didn't exist. Ignore it.
1406 if e.returncode != 5:
1407 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001408 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001409 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001410 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1411 '^\\+refs/branch-heads/\\*:.*$']
1412 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001413 if hasattr(options, 'with_tags') and options.with_tags:
1414 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1415 '+refs/tags/*:refs/tags/*',
1416 '^\\+refs/tags/\\*:.*$']
1417 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001418
John Budorick882c91e2018-07-12 22:11:41 +00001419 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001420 """Attempts to fetch |revision| if not available in local repo.
1421
1422 Returns possibly updated revision."""
Edward Lemure0ba7b82020-03-11 20:31:32 +00001423 if not scm.GIT.IsValidRevision(self.checkout_path, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001424 self._Fetch(options, refspec=revision)
1425 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1426 return revision
1427
Edward Lemur24146be2019-08-01 21:44:52 +00001428 def _Run(self, args, options, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001429 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001430 kwargs.setdefault('cwd', self.checkout_path)
Edward Lemur24146be2019-08-01 21:44:52 +00001431 kwargs.setdefault('filter_fn', self.filter)
1432 kwargs.setdefault('show_header', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001433 env = scm.GIT.ApplyEnvVars(kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +00001434
agable@chromium.org772efaf2014-04-01 02:35:44 +00001435 cmd = ['git'] + args
Edward Lemur24146be2019-08-01 21:44:52 +00001436 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001437
1438
1439class CipdPackage(object):
1440 """A representation of a single CIPD package."""
1441
John Budorickd3ba72b2018-03-20 12:27:42 -07001442 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001443 self._authority_for_subdir = authority_for_subdir
1444 self._name = name
1445 self._version = version
1446
1447 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001448 def authority_for_subdir(self):
1449 """Whether this package has authority to act on behalf of its subdir.
1450
1451 Some operations should only be performed once per subdirectory. A package
1452 that has authority for its subdirectory is the only package that should
1453 perform such operations.
1454
1455 Returns:
1456 bool; whether this package has subdir authority.
1457 """
1458 return self._authority_for_subdir
1459
1460 @property
1461 def name(self):
1462 return self._name
1463
1464 @property
1465 def version(self):
1466 return self._version
1467
1468
1469class CipdRoot(object):
1470 """A representation of a single CIPD root."""
1471 def __init__(self, root_dir, service_url):
1472 self._all_packages = set()
1473 self._mutator_lock = threading.Lock()
1474 self._packages_by_subdir = collections.defaultdict(list)
1475 self._root_dir = root_dir
1476 self._service_url = service_url
1477
1478 def add_package(self, subdir, package, version):
1479 """Adds a package to this CIPD root.
1480
1481 As far as clients are concerned, this grants both root and subdir authority
1482 to packages arbitrarily. (The implementation grants root authority to the
1483 first package added and subdir authority to the first package added for that
1484 subdir, but clients should not depend on or expect that behavior.)
1485
1486 Args:
1487 subdir: str; relative path to where the package should be installed from
1488 the cipd root directory.
1489 package: str; the cipd package name.
1490 version: str; the cipd package version.
1491 Returns:
1492 CipdPackage; the package that was created and added to this root.
1493 """
1494 with self._mutator_lock:
1495 cipd_package = CipdPackage(
1496 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001497 not self._packages_by_subdir[subdir])
1498 self._all_packages.add(cipd_package)
1499 self._packages_by_subdir[subdir].append(cipd_package)
1500 return cipd_package
1501
1502 def packages(self, subdir):
1503 """Get the list of configured packages for the given subdir."""
1504 return list(self._packages_by_subdir[subdir])
1505
1506 def clobber(self):
1507 """Remove the .cipd directory.
1508
1509 This is useful for forcing ensure to redownload and reinitialize all
1510 packages.
1511 """
1512 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001513 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001514 try:
1515 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1516 except OSError:
1517 if os.path.exists(cipd_cache_dir):
1518 raise
1519
1520 @contextlib.contextmanager
1521 def _create_ensure_file(self):
1522 try:
Edward Lesmes05934952019-12-19 20:38:09 +00001523 contents = '$ParanoidMode CheckPresence\n\n'
1524 for subdir, packages in sorted(self._packages_by_subdir.items()):
1525 contents += '@Subdir %s\n' % subdir
1526 for package in sorted(packages, key=lambda p: p.name):
1527 contents += '%s %s\n' % (package.name, package.version)
1528 contents += '\n'
John Budorick0f7b2002018-01-19 15:46:17 -08001529 ensure_file = None
1530 with tempfile.NamedTemporaryFile(
Edward Lesmes05934952019-12-19 20:38:09 +00001531 suffix='.ensure', delete=False, mode='wb') as ensure_file:
1532 ensure_file.write(contents.encode('utf-8', 'replace'))
John Budorick0f7b2002018-01-19 15:46:17 -08001533 yield ensure_file.name
1534 finally:
1535 if ensure_file is not None and os.path.exists(ensure_file.name):
1536 os.remove(ensure_file.name)
1537
1538 def ensure(self):
1539 """Run `cipd ensure`."""
1540 with self._mutator_lock:
1541 with self._create_ensure_file() as ensure_file:
1542 cmd = [
1543 'cipd', 'ensure',
1544 '-log-level', 'error',
1545 '-root', self.root_dir,
1546 '-ensure-file', ensure_file,
1547 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001548 gclient_utils.CheckCallAndFilter(
1549 cmd, print_stdout=True, show_header=True)
John Budorick0f7b2002018-01-19 15:46:17 -08001550
John Budorickd3ba72b2018-03-20 12:27:42 -07001551 def run(self, command):
1552 if command == 'update':
1553 self.ensure()
1554 elif command == 'revert':
1555 self.clobber()
1556 self.ensure()
1557
John Budorick0f7b2002018-01-19 15:46:17 -08001558 def created_package(self, package):
1559 """Checks whether this root created the given package.
1560
1561 Args:
1562 package: CipdPackage; the package to check.
1563 Returns:
1564 bool; whether this root created the given package.
1565 """
1566 return package in self._all_packages
1567
1568 @property
1569 def root_dir(self):
1570 return self._root_dir
1571
1572 @property
1573 def service_url(self):
1574 return self._service_url
1575
1576
1577class CipdWrapper(SCMWrapper):
1578 """Wrapper for CIPD.
1579
1580 Currently only supports chrome-infra-packages.appspot.com.
1581 """
John Budorick3929e9e2018-02-04 18:18:07 -08001582 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001583
1584 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1585 out_cb=None, root=None, package=None):
1586 super(CipdWrapper, self).__init__(
1587 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1588 out_cb=out_cb)
1589 assert root.created_package(package)
1590 self._package = package
1591 self._root = root
1592
1593 #override
1594 def GetCacheMirror(self):
1595 return None
1596
1597 #override
1598 def GetActualRemoteURL(self, options):
1599 return self._root.service_url
1600
1601 #override
1602 def DoesRemoteURLMatch(self, options):
1603 del options
1604 return True
1605
1606 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001607 """Does nothing.
1608
1609 CIPD packages should be reverted at the root by running
1610 `CipdRoot.run('revert')`.
1611 """
John Budorick0f7b2002018-01-19 15:46:17 -08001612
1613 def diff(self, options, args, file_list):
1614 """CIPD has no notion of diffing."""
John Budorick0f7b2002018-01-19 15:46:17 -08001615
1616 def pack(self, options, args, file_list):
1617 """CIPD has no notion of diffing."""
John Budorick0f7b2002018-01-19 15:46:17 -08001618
1619 def revinfo(self, options, args, file_list):
1620 """Grab the instance ID."""
1621 try:
1622 tmpdir = tempfile.mkdtemp()
1623 describe_json_path = os.path.join(tmpdir, 'describe.json')
1624 cmd = [
1625 'cipd', 'describe',
1626 self._package.name,
1627 '-log-level', 'error',
1628 '-version', self._package.version,
1629 '-json-output', describe_json_path
1630 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001631 gclient_utils.CheckCallAndFilter(cmd)
John Budorick0f7b2002018-01-19 15:46:17 -08001632 with open(describe_json_path) as f:
1633 describe_json = json.load(f)
1634 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1635 finally:
1636 gclient_utils.rmtree(tmpdir)
1637
1638 def status(self, options, args, file_list):
1639 pass
1640
1641 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001642 """Does nothing.
1643
1644 CIPD packages should be updated at the root by running
1645 `CipdRoot.run('update')`.
1646 """