blob: c51fee811a7b5135b4d2e4f69ce103415646a5d0 [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
Aravind Vasudevan14e6d232022-06-02 20:42:16 +0000211 _is_env_cog = None
212
213 @staticmethod
214 def _IsCog():
215 """Returns true if the env is cog"""
216 if not GitWrapper._is_env_cog:
217 GitWrapper._is_env_cog = os.getcwd().startswith('/google/src/cloud')
218
219 return GitWrapper._is_env_cog
220
Robert Iannuccia19649b2018-06-29 16:31:45 +0000221 @property
222 def cache_dir(self):
223 try:
224 return git_cache.Mirror.GetCachePath()
225 except RuntimeError:
226 return None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000227
John Budorick0f7b2002018-01-19 15:46:17 -0800228 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000229 """Removes 'git+' fake prefix from git URL."""
Henrique Ferreiroe72279d2019-04-17 12:01:50 +0000230 if url and (url.startswith('git+http://') or
231 url.startswith('git+https://')):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000232 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800233 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000234 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
235 if self.out_cb:
236 filter_kwargs['predicate'] = self.out_cb
237 self.filter = gclient_utils.GitFilter(**filter_kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +0000238 self._running_under_rosetta = None
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000239
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000240 def GetCheckoutRoot(self):
241 return scm.GIT.GetCheckoutRoot(self.checkout_path)
242
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000243 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000244 """Returns the given revision's date in ISO-8601 format (which contains the
245 time zone)."""
246 # TODO(floitsch): get the time-stamp of the given revision and not just the
247 # time-stamp of the currently checked out revision.
248 return self._Capture(['log', '-n', '1', '--format=%ai'])
249
Aaron Gablef4068aa2017-12-12 15:14:09 -0800250 def _GetDiffFilenames(self, base):
251 """Returns the names of files modified since base."""
252 return self._Capture(
Raul Tambrecd862e32019-05-10 21:19:00 +0000253 # Filter to remove base if it is None.
254 list(filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only',
255 base])
256 )).split()
Aaron Gablef4068aa2017-12-12 15:14:09 -0800257
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000258 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 15:45:56 -0800259 _, revision = gclient_utils.SplitUrlRevision(self.url)
260 if not revision:
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000261 revision = 'refs/remotes/%s/main' % self.remote
Aaron Gable1853f662018-02-12 15:45:56 -0800262 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000263
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000264 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000265 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000266 repository.
267
268 The patch file is generated from a diff of the merge base of HEAD and
269 its upstream branch.
270 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700271 try:
272 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
273 except subprocess2.CalledProcessError:
274 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000275 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700276 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000277 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000278 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000279
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800280 def _Scrub(self, target, options):
281 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000282 quiet = []
283 if not options.verbose:
284 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800285 self._Run(['reset', '--hard', target] + quiet, options)
286 if options.force and options.delete_unversioned_trees:
287 # where `target` is a commit that contains both upper and lower case
288 # versions of the same file on a case insensitive filesystem, we are
289 # actually in a broken state here. The index will have both 'a' and 'A',
290 # but only one of them will exist on the disk. To progress, we delete
291 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800292 output = self._Capture([
293 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800294 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800295 # --porcelain (v1) looks like:
296 # XY filename
297 try:
298 filename = line[3:]
299 self.Print('_____ Deleting residual after reset: %r.' % filename)
300 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800301 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800302 except OSError:
303 pass
304
John Budorick882c91e2018-07-12 22:11:41 +0000305 def _FetchAndReset(self, revision, file_list, options):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800306 """Equivalent to git fetch; git reset."""
Edward Lemur579c9862018-07-13 23:17:51 +0000307 self._SetFetchConfig(options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000308
dnj@chromium.org680f2172014-06-25 00:39:32 +0000309 self._Fetch(options, prune=True, quiet=options.verbose)
John Budorick882c91e2018-07-12 22:11:41 +0000310 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000311 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800312 files = self._Capture(
313 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambreb946b232019-03-26 14:48:46 +0000314 file_list.extend(
Edward Lemur26a8b9f2019-08-15 20:46:44 +0000315 [os.path.join(self.checkout_path, f) for f in files])
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000316
szager@chromium.org8a139702014-06-20 15:55:01 +0000317 def _DisableHooks(self):
318 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
319 if not os.path.isdir(hook_dir):
320 return
321 for f in os.listdir(hook_dir):
322 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000323 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
324 if os.path.exists(disabled_hook_path):
325 os.remove(disabled_hook_path)
326 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000327
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000328 def _maybe_break_locks(self, options):
329 """This removes all .lock files from this repo's .git directory, if the
330 user passed the --break_repo_locks command line flag.
331
332 In particular, this will cleanup index.lock files, as well as ref lock
333 files.
334 """
335 if options.break_repo_locks:
336 git_dir = os.path.join(self.checkout_path, '.git')
337 for path, _, filenames in os.walk(git_dir):
338 for filename in filenames:
339 if filename.endswith('.lock'):
340 to_break = os.path.join(path, filename)
341 self.Print('breaking lock: %s' % (to_break,))
342 try:
343 os.remove(to_break)
344 except OSError as ex:
345 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
346 raise
347
Ravi Mistryc848a4e2022-03-10 18:19:59 +0000348 def _download_topics(self, patch_rev, googlesource_url):
349 """This method returns new patch_revs to process that have the same topic.
350
351 It does the following:
352 1. Finds the topic of the Gerrit change specified in the patch_rev.
353 2. Find all changes with that topic.
354 3. Append patch_rev of the changes with the same topic to the patch_revs
355 to process.
356 4. Returns the new patch_revs to process.
357 """
358 patch_revs_to_process = []
359 # Parse the patch_rev to extract the CL and patchset.
360 patch_rev_tokens = patch_rev.split('/')
361 change = patch_rev_tokens[-2]
362 # Parse the googlesource_url.
363 tokens = re.search(
364 '//(.+).googlesource.com/(.+?)(?:\.git)?$', googlesource_url)
365 if not tokens or len(tokens.groups()) != 2:
366 # googlesource_url is not in the expected format.
367 return patch_revs_to_process
368
369 # parse the gerrit host and repo out of googlesource_url.
370 host, repo = tokens.groups()[:2]
371 gerrit_host_url = '%s-review.googlesource.com' % host
372
373 # 1. Find the topic of the Gerrit change specified in the patch_rev.
374 change_object = gerrit_util.GetChange(gerrit_host_url, change)
375 topic = change_object.get('topic')
376 if not topic:
377 # This change has no topic set.
378 return patch_revs_to_process
379
380 # 2. Find all changes with that topic.
381 changes_with_same_topic = gerrit_util.QueryChanges(
382 gerrit_host_url,
383 [('topic', topic), ('status', 'open'), ('repo', repo)],
384 o_params=['ALL_REVISIONS'])
385 for c in changes_with_same_topic:
386 if str(c['_number']) == change:
387 # This change is already in the patch_rev.
388 continue
389 self.Print('Found CL %d with the topic name %s' % (
390 c['_number'], topic))
391 # 3. Append patch_rev of the changes with the same topic to the
392 # patch_revs to process.
393 curr_rev = c['current_revision']
394 new_patch_rev = c['revisions'][curr_rev]['ref']
395 patch_revs_to_process.append(new_patch_rev)
396
397 # 4. Return the new patch_revs to process.
398 return patch_revs_to_process
399
Edward Lemur3acbc742019-05-30 17:57:35 +0000400 def apply_patch_ref(self, patch_repo, patch_rev, target_rev, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000401 file_list):
402 """Apply a patch on top of the revision we're synced at.
403
Edward Lemur3acbc742019-05-30 17:57:35 +0000404 The patch ref is given by |patch_repo|@|patch_rev|.
405 |target_rev| is usually the branch that the |patch_rev| was uploaded against
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000406 (e.g. 'refs/heads/main'), but this is not required.
Edward Lemur3acbc742019-05-30 17:57:35 +0000407
408 We cherry-pick all commits reachable from |patch_rev| on top of the curret
409 HEAD, excluding those reachable from |target_rev|
410 (i.e. git cherry-pick target_rev..patch_rev).
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000411
412 Graphically, it looks like this:
413
Edward Lemur3acbc742019-05-30 17:57:35 +0000414 ... -> o -> [possibly already landed commits] -> target_rev
415 \
416 -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000417
Edward Lemur3acbc742019-05-30 17:57:35 +0000418 The final checkout state is then:
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000419
Edward Lemur3acbc742019-05-30 17:57:35 +0000420 ... -> HEAD -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000421
422 After application, if |options.reset_patch_ref| is specified, we soft reset
Edward Lemur3acbc742019-05-30 17:57:35 +0000423 the cherry-picked changes, keeping them in git index only.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000424
425 Args:
Edward Lemur3acbc742019-05-30 17:57:35 +0000426 patch_repo: The patch origin.
427 e.g. 'https://foo.googlesource.com/bar'
428 patch_rev: The revision to patch.
429 e.g. 'refs/changes/1234/34/1'.
430 target_rev: The revision to use when finding the merge base.
431 Typically, the branch that the patch was uploaded against.
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000432 e.g. 'refs/heads/main' or 'refs/heads/infra/config'.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000433 options: The options passed to gclient.
434 file_list: A list where modified files will be appended.
435 """
436
Edward Lemurca7d8812018-07-24 17:42:45 +0000437 # Abort any cherry-picks in progress.
438 try:
439 self._Capture(['cherry-pick', '--abort'])
440 except subprocess2.CalledProcessError:
441 pass
442
Edward Lesmesc621b212018-03-21 20:26:56 -0400443 base_rev = self._Capture(['rev-parse', 'HEAD'])
Edward Lemura0ffbe42019-05-01 16:52:18 +0000444
Edward Lemur3acbc742019-05-30 17:57:35 +0000445 if not target_rev:
Edward Lemur4c5c8ab2019-06-07 15:58:13 +0000446 raise gclient_utils.Error('A target revision for the patch must be given')
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000447
448 if target_rev.startswith(('refs/heads/', 'refs/branch-heads')):
Edward Lesmesf627d9f2020-07-23 19:50:50 +0000449 # If |target_rev| is in refs/heads/** or refs/branch-heads/**, try first
450 # to find the corresponding remote ref for it, since |target_rev| might
451 # point to a local ref which is not up to date with the corresponding
452 # remote ref.
Edward Lemur3acbc742019-05-30 17:57:35 +0000453 remote_ref = ''.join(scm.GIT.RefToRemoteRef(target_rev, self.remote))
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000454 self.Print('Trying the corresponding remote ref for %r: %r\n' % (
Edward Lemur3acbc742019-05-30 17:57:35 +0000455 target_rev, remote_ref))
456 if scm.GIT.IsValidRevision(self.checkout_path, remote_ref):
Josip Sokcevic3d7cbce2021-10-05 20:48:04 +0000457 # refs/remotes may need to be updated to cleanly cherry-pick changes.
458 # See https://crbug.com/1255178.
459 self._Capture(['fetch', '--no-tags', self.remote, target_rev])
Edward Lemur3acbc742019-05-30 17:57:35 +0000460 target_rev = remote_ref
461 elif not scm.GIT.IsValidRevision(self.checkout_path, target_rev):
462 # Fetch |target_rev| if it's not already available.
463 url, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmes07a68342021-04-20 23:39:30 +0000464 mirror = self._GetMirror(url, options, target_rev, target_rev)
Edward Lemur3acbc742019-05-30 17:57:35 +0000465 if mirror:
466 rev_type = 'branch' if target_rev.startswith('refs/') else 'hash'
467 self._UpdateMirrorIfNotContains(mirror, options, rev_type, target_rev)
468 self._Fetch(options, refspec=target_rev)
Edward Lemura0ffbe42019-05-01 16:52:18 +0000469
Ravi Mistryecda7822022-02-28 16:22:20 +0000470 patch_revs_to_process = [patch_rev]
471
472 if hasattr(options, 'download_topics') and options.download_topics:
Ravi Mistryc848a4e2022-03-10 18:19:59 +0000473 patch_revs_to_process_from_topics = self._download_topics(
474 patch_rev, self.url)
475 patch_revs_to_process.extend(patch_revs_to_process_from_topics)
Ravi Mistryecda7822022-02-28 16:22:20 +0000476
Edward Lesmesc621b212018-03-21 20:26:56 -0400477 self._Capture(['reset', '--hard'])
Ravi Mistryecda7822022-02-28 16:22:20 +0000478 for pr in patch_revs_to_process:
479 self.Print('===Applying patch===')
480 self.Print('Revision to patch is %r @ %r.' % (patch_repo, pr))
481 self.Print('Current dir is %r' % self.checkout_path)
482 self._Capture(['fetch', '--no-tags', patch_repo, pr])
483 pr = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400484
Ravi Mistryecda7822022-02-28 16:22:20 +0000485 if not options.rebase_patch_ref:
486 self._Capture(['checkout', pr])
487 # Adjust base_rev to be the first parent of our checked out patch ref;
488 # This will allow us to correctly extend `file_list`, and will show the
489 # correct file-list to programs which do `git diff --cached` expecting
490 # to see the patch diff.
491 base_rev = self._Capture(['rev-parse', pr+'~'])
492 else:
493 self.Print('Will cherrypick %r .. %r on top of %r.' % (
494 target_rev, pr, base_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000495 try:
Ravi Mistryecda7822022-02-28 16:22:20 +0000496 if scm.GIT.IsAncestor(self.checkout_path, pr, target_rev):
497 if len(patch_revs_to_process) > 1:
498 # If there are multiple patch_revs_to_process then we do not want
499 # want to invalidate a previous patch so throw an error.
500 raise gclient_utils.Error(
501 'patch_rev %s is an ancestor of target_rev %s. This '
502 'situation is unsupported when we need to apply multiple '
503 'patch_revs: %s' % (pr, target_rev, patch_revs_to_process))
504 # If |patch_rev| is an ancestor of |target_rev|, check it out.
505 self._Capture(['checkout', pr])
506 else:
507 # If a change was uploaded on top of another change, which has
508 # already landed, one of the commits in the cherry-pick range will
509 # be redundant, since it has already landed and its changes
510 # incorporated in the tree.
511 # We pass '--keep-redundant-commits' to ignore those changes.
512 self._Capture(['cherry-pick', target_rev + '..' + pr,
513 '--keep-redundant-commits'])
Edward Lemurca7d8812018-07-24 17:42:45 +0000514
Ravi Mistryecda7822022-02-28 16:22:20 +0000515 except subprocess2.CalledProcessError as e:
516 self.Print('Failed to apply patch.')
517 self.Print('Revision to patch was %r @ %r.' % (patch_repo, pr))
518 self.Print('Tried to cherrypick %r .. %r on top of %r.' % (
519 target_rev, pr, base_rev))
520 self.Print('Current dir is %r' % self.checkout_path)
521 self.Print('git returned non-zero exit status %s:\n%s' % (
522 e.returncode, e.stderr.decode('utf-8')))
523 # Print the current status so that developers know what changes caused
524 # the patch failure, since git cherry-pick doesn't show that
525 # information.
526 self.Print(self._Capture(['status']))
527 try:
528 self._Capture(['cherry-pick', '--abort'])
529 except subprocess2.CalledProcessError:
530 pass
531 raise
532
533 if file_list is not None:
534 file_list.extend(self._GetDiffFilenames(base_rev))
Edward Lemurca7d8812018-07-24 17:42:45 +0000535
Edward Lesmesc621b212018-03-21 20:26:56 -0400536 if options.reset_patch_ref:
537 self._Capture(['reset', '--soft', base_rev])
538
msb@chromium.orge28e4982009-09-25 20:51:45 +0000539 def update(self, options, args, file_list):
540 """Runs git to update or transparently checkout the working copy.
541
542 All updated files will be appended to file_list.
543
544 Raises:
545 Error: if can't get URL for relative path.
546 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000547 if args:
548 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
549
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000550 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000551
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000552 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000553 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000554 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000555 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000556 # Override the revision number.
557 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000558 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000559 # Check again for a revision in case an initial ref was specified
560 # in the url, for example bla.git@refs/heads/custombranch
561 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000562 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000563 if not revision:
Josip Sokcevic091f5ac2021-01-14 23:14:21 +0000564 # If a dependency is not pinned, track the default remote branch.
565 revision = scm.GIT.GetRemoteHeadRef(self.checkout_path, self.url,
566 self.remote)
Edward Lesmes4ea67bb2021-04-20 17:33:52 +0000567 if revision.startswith('origin/'):
568 revision = 'refs/remotes/' + revision
msb@chromium.orge28e4982009-09-25 20:51:45 +0000569
Tomasz Wiszkowskid4e66882021-08-19 21:35:09 +0000570 if managed and platform.system() == 'Windows':
szager@chromium.org8a139702014-06-20 15:55:01 +0000571 self._DisableHooks()
572
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000573 printed_path = False
574 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000575 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700576 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000577 verbose = ['--verbose']
578 printed_path = True
579
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000580 revision_ref = revision
581 if ':' in revision:
582 revision_ref, _, revision = revision.partition(':')
583
Edward Lesmes8073a502020-04-15 02:11:14 +0000584 if revision_ref.startswith('refs/branch-heads'):
585 options.with_branch_heads = True
586
Edward Lesmes07a68342021-04-20 23:39:30 +0000587 mirror = self._GetMirror(url, options, revision, revision_ref)
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000588 if mirror:
589 url = mirror.mirror_path
Edward Lemurdb5c5ad2019-03-07 16:00:24 +0000590
John Budorick882c91e2018-07-12 22:11:41 +0000591 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
592 if remote_ref:
593 # Rewrite remote refs to their local equivalents.
594 revision = ''.join(remote_ref)
595 rev_type = "branch"
596 elif revision.startswith('refs/'):
597 # Local branch? We probably don't want to support, since DEPS should
598 # always specify branches as they are in the upstream repo.
599 rev_type = "branch"
600 else:
601 # hash is also a tag, only make a distinction at checkout
602 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000603
primiano@chromium.org1c127382015-02-17 11:15:40 +0000604 # If we are going to introduce a new project, there is a possibility that
605 # we are syncing back to a state where the project was originally a
606 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
607 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
608 # In such case, we might have a backup of the former .git folder, which can
609 # be used to avoid re-fetching the entire repo again (useful for bisects).
610 backup_dir = self.GetGitBackupDirPath()
611 target_dir = os.path.join(self.checkout_path, '.git')
612 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
613 gclient_utils.safe_makedirs(self.checkout_path)
614 os.rename(backup_dir, target_dir)
615 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800616 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000617
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000618 if (not os.path.exists(self.checkout_path) or
619 (os.path.isdir(self.checkout_path) and
620 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
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)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000623 try:
John Budorick882c91e2018-07-12 22:11:41 +0000624 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000625 except subprocess2.CalledProcessError:
626 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000627 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000628 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800629 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20 +0000630 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambrec2f74c12019-03-19 05:55:53 +0000631 file_list.extend(
Edward Lemur979fa782019-08-13 22:44:05 +0000632 [os.path.join(self.checkout_path, f) for f in files])
John Budorick21a51b32018-09-19 19:39:20 +0000633 if mirror:
634 self._Capture(
635 ['remote', 'set-url', '--push', 'origin', mirror.url])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000636 if not verbose:
637 # Make the output a little prettier. It's nice to have some whitespace
638 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000639 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000640 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000641
John Budorick21a51b32018-09-19 19:39:20 +0000642 if mirror:
643 self._Capture(
644 ['remote', 'set-url', '--push', 'origin', mirror.url])
645
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000646 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000647 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000648 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
649 return self._Capture(['rev-parse', '--verify', 'HEAD'])
650
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000651 self._maybe_break_locks(options)
652
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000653 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000654 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000655
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000656 # See if the url has changed (the unittests use git://foo for the url, let
657 # that through).
658 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
659 return_early = False
660 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
661 # unit test pass. (and update the comment above)
662 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
663 # This allows devs to use experimental repos which have a different url
664 # but whose branch(s) are the same as official repos.
Raul Tambrecd862e32019-05-10 21:19:00 +0000665 if (current_url.rstrip('/') != url.rstrip('/') and url != 'git://foo' and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000666 subprocess2.capture(
Aaron Gableac9b0f32019-04-18 17:38:37 +0000667 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000668 cwd=self.checkout_path).strip() != 'False'):
Anthony Polito486f1812020-08-04 23:40:33 +0000669 self.Print('_____ switching %s from %s to new upstream %s' % (
670 self.relpath, current_url, url))
iannucci@chromium.org78514212014-08-20 23:08:00 +0000671 if not (options.force or options.reset):
672 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700673 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000674 # Switch over to the new upstream
675 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000676 if mirror:
677 with open(os.path.join(
678 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
679 'w') as fh:
680 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000681 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
682 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000683
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000684 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000685 else:
John Budorick882c91e2018-07-12 22:11:41 +0000686 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000687
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000688 if return_early:
689 return self._Capture(['rev-parse', '--verify', 'HEAD'])
690
msb@chromium.org5bde4852009-12-14 16:47:12 +0000691 cur_branch = self._GetCurrentBranch()
692
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000693 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000694 # 0) HEAD is detached. Probably from our initial clone.
695 # - make sure HEAD is contained by a named ref, then update.
696 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700697 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000698 # - try to rebase onto the new hash or branch
699 # 2) current branch is tracking a remote branch with local committed
700 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000701 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000702 # 3) current branch is tracking a remote branch w/or w/out changes, and
703 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000704 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000705 # 4) current branch is tracking a remote branch, but DEPS switches to a
706 # different remote branch, and
707 # a) current branch has no local changes, and --force:
708 # - checkout new branch
709 # b) current branch has local changes, and --force and --reset:
710 # - checkout new branch
711 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000712
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000713 # GetUpstreamBranch returns something like 'refs/remotes/origin/main' for
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000714 # a tracking branch
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000715 # or 'main' if not a tracking branch (it's based on a specific rev/hash)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000716 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000717 if cur_branch is None:
718 upstream_branch = None
719 current_type = "detached"
720 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000721 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000722 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
723 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
724 current_type = "hash"
725 logging.debug("Current branch is not tracking an upstream (remote)"
726 " branch.")
727 elif upstream_branch.startswith('refs/remotes'):
728 current_type = "branch"
729 else:
730 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000731
Edward Lemur579c9862018-07-13 23:17:51 +0000732 self._SetFetchConfig(options)
Edward Lemur579c9862018-07-13 23:17:51 +0000733
Michael Spang73fac912019-03-08 18:44:19 +0000734 # Fetch upstream if we don't already have |revision|.
John Budorick882c91e2018-07-12 22:11:41 +0000735 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
Michael Spang73fac912019-03-08 18:44:19 +0000736 self._Fetch(options, prune=options.force)
737
738 if not scm.GIT.IsValidRevision(self.checkout_path, revision,
739 sha_only=True):
740 # Update the remotes first so we have all the refs.
741 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
742 cwd=self.checkout_path)
743 if verbose:
744 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000745
John Budorick882c91e2018-07-12 22:11:41 +0000746 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200747
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000748 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000749 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000750 target = 'HEAD'
751 if options.upstream and upstream_branch:
752 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800753 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000754
msb@chromium.org786fb682010-06-02 15:16:23 +0000755 if current_type == 'detached':
756 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800757 # We just did a Scrub, this is as clean as it's going to get. In
758 # particular if HEAD is a commit that contains two versions of the same
759 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
760 # to actually "Clean" the checkout; that commit is uncheckoutable on this
761 # system. The best we can do is carry forward to the checkout step.
762 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000763 self._CheckClean(revision)
764 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000765 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000766 self.Print('Up-to-date; skipping checkout.')
767 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000768 # 'git checkout' may need to overwrite existing untracked files. Allow
769 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000770 self._Checkout(
771 options,
John Budorick882c91e2018-07-12 22:11:41 +0000772 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000773 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000774 quiet=True,
775 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000776 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000777 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000778 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000779 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700780 # Can't find a merge-base since we don't know our upstream. That makes
781 # this command VERY likely to produce a rebase failure. For now we
782 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000783 upstream_branch = self.remote
784 if options.revision or deps_revision:
785 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700786 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700787 printed_path=printed_path, merge=options.merge)
788 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000789 elif rev_type == 'hash':
790 # case 2
791 self._AttemptRebase(upstream_branch, file_list, options,
792 newbase=revision, printed_path=printed_path,
793 merge=options.merge)
794 printed_path = True
795 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000796 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000797 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000798 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000799 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000800 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000801 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000802 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000803 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
804 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000805 force_switch = False
806 if options.force:
807 try:
John Budorick882c91e2018-07-12 22:11:41 +0000808 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000809 # case 4a
810 force_switch = True
811 except gclient_utils.Error as e:
812 if options.reset:
813 # case 4b
814 force_switch = True
815 else:
816 switch_error = '%s\n%s' % (e.message, switch_error)
817 if force_switch:
818 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000819 (upstream_branch, new_base))
820 switch_branch = 'gclient_' + remote_ref[1]
821 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000822 self._Checkout(options, switch_branch, force=True, quiet=True)
823 else:
824 # case 4c
825 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000826 else:
827 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800828 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000829 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000830 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000831 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000832 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000833 if options.merge:
834 merge_args.append('--ff')
835 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000836 merge_args.append('--ff-only')
837 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000838 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000839 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700840 rebase_files = []
Edward Lemur979fa782019-08-13 22:44:05 +0000841 if re.match(b'fatal: Not possible to fast-forward, aborting.',
842 e.stderr):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000843 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000844 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700845 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000846 printed_path = True
847 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000848 if not options.auto_rebase:
849 try:
850 action = self._AskForData(
851 'Cannot %s, attempt to rebase? '
852 '(y)es / (q)uit / (s)kip : ' %
853 ('merge' if options.merge else 'fast-forward merge'),
854 options)
855 except ValueError:
856 raise gclient_utils.Error('Invalid Character')
857 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700858 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000859 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000860 printed_path = True
861 break
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000862
863 if re.match(r'quit|q', action, re.I):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000864 raise gclient_utils.Error("Can't fast-forward, please merge or "
865 "rebase manually.\n"
866 "cd %s && git " % self.checkout_path
867 + "rebase %s" % upstream_branch)
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000868
869 if re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000870 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000871 return
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000872
873 self.Print('Input not recognized')
Edward Lemur979fa782019-08-13 22:44:05 +0000874 elif re.match(b"error: Your local changes to '.*' would be "
875 b"overwritten by merge. Aborting.\nPlease, commit your "
876 b"changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000877 e.stderr):
878 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000879 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700880 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000881 printed_path = True
Edward Lemur979fa782019-08-13 22:44:05 +0000882 raise gclient_utils.Error(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000883 else:
884 # Some other problem happened with the merge
885 logging.error("Error during fast-forward merge in %s!" % self.relpath)
Edward Lemur979fa782019-08-13 22:44:05 +0000886 self.Print(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000887 raise
888 else:
889 # Fast-forward merge was successful
890 if not re.match('Already up-to-date.', merge_output) or verbose:
891 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700892 self.Print('_____ %s at %s' % (self.relpath, revision),
893 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000894 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000895 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000896 if not verbose:
897 # Make the output a little prettier. It's nice to have some
898 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000899 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000900
agablec3937b92016-10-25 10:13:03 -0700901 if file_list is not None:
902 file_list.extend(
903 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000904
905 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000906 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700907 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000908 '\nConflict while rebasing this branch.\n'
909 'Fix the conflict and run gclient again.\n'
910 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700911 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000912
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000913 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000914 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
915 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000916
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000917 # If --reset and --delete_unversioned_trees are specified, remove any
918 # untracked directories.
919 if options.reset and options.delete_unversioned_trees:
920 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
921 # merge-base by default), so doesn't include untracked files. So we use
922 # 'git ls-files --directory --others --exclude-standard' here directly.
923 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800924 ['-c', 'core.quotePath=false', 'ls-files',
925 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000926 self.checkout_path)
927 for path in (p for p in paths.splitlines() if p.endswith('/')):
928 full_path = os.path.join(self.checkout_path, path)
929 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000930 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000931 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000932
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000933 return self._Capture(['rev-parse', '--verify', 'HEAD'])
934
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000935 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000936 """Reverts local modifications.
937
938 All reverted files will be appended to file_list.
939 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000940 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000941 # revert won't work if the directory doesn't exist. It needs to
942 # checkout instead.
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000943 self.Print('_____ %s is missing, syncing instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000944 # Don't reuse the args.
945 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000946
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000947 default_rev = "refs/heads/main"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000948 if options.upstream:
949 if self._GetCurrentBranch():
950 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
951 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000952 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000953 if not deps_revision:
954 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000955 if deps_revision.startswith('refs/heads/'):
956 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700957 try:
958 deps_revision = self.GetUsableRev(deps_revision, options)
959 except NoUsableRevError as e:
960 # If the DEPS entry's url and hash changed, try to update the origin.
961 # See also http://crbug.com/520067.
John Budorickd94f8ea2020-03-27 15:55:24 +0000962 logging.warning(
963 "Couldn't find usable revision, will retrying to update instead: %s",
smutae7ea312016-07-18 11:59:41 -0700964 e.message)
965 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000966
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000967 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800968 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000969
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800970 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000971 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000972
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000973 if file_list is not None:
974 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
975
976 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000977 """Returns revision"""
978 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000979
msb@chromium.orge28e4982009-09-25 20:51:45 +0000980 def runhooks(self, options, args, file_list):
981 self.status(options, args, file_list)
982
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000983 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000984 """Display status information."""
985 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000986 self.Print('________ couldn\'t run status in %s:\n'
987 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000988 else:
Anthony Politobb457342019-11-15 22:26:01 +0000989 merge_base = []
990 if self.url:
991 _, base_rev = gclient_utils.SplitUrlRevision(self.url)
992 if base_rev:
993 merge_base = [base_rev]
Aaron Gablef4068aa2017-12-12 15:14:09 -0800994 self._Run(
995 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
Edward Lemur24146be2019-08-01 21:44:52 +0000996 options, always_show_header=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000997 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800998 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000999 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +00001000
smutae7ea312016-07-18 11:59:41 -07001001 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -07001002 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -07001003 sha1 = None
1004 if not os.path.isdir(self.checkout_path):
1005 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -08001006 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -07001007
1008 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
1009 sha1 = rev
smutae7ea312016-07-18 11:59:41 -07001010 else:
agable41e3a6c2016-10-20 11:36:56 -07001011 # May exist in origin, but we don't have it yet, so fetch and look
1012 # again.
1013 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -07001014 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
1015 sha1 = rev
smutae7ea312016-07-18 11:59:41 -07001016
1017 if not sha1:
1018 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -08001019 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -07001020
1021 return sha1
1022
primiano@chromium.org1c127382015-02-17 11:15:40 +00001023 def GetGitBackupDirPath(self):
1024 """Returns the path where the .git folder for the current project can be
1025 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
1026 return os.path.join(self._root_dir,
1027 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
1028
Edward Lesmes07a68342021-04-20 23:39:30 +00001029 def _GetMirror(self, url, options, revision=None, revision_ref=None):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +00001030 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +00001031 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +00001032 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +00001033 mirror_kwargs = {
1034 'print_func': self.filter,
Edward Lesmes07a68342021-04-20 23:39:30 +00001035 'refs': [],
1036 'commits': [],
hinoka@google.comb1b54572014-04-16 22:29:23 +00001037 }
hinoka@google.comb1b54572014-04-16 22:29:23 +00001038 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
1039 mirror_kwargs['refs'].append('refs/branch-heads/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +00001040 elif revision_ref and revision_ref.startswith('refs/branch-heads/'):
1041 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001042 if hasattr(options, 'with_tags') and options.with_tags:
1043 mirror_kwargs['refs'].append('refs/tags/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +00001044 elif revision_ref and revision_ref.startswith('refs/tags/'):
1045 mirror_kwargs['refs'].append(revision_ref)
Edward Lesmes07a68342021-04-20 23:39:30 +00001046 if revision and not revision.startswith('refs/'):
1047 mirror_kwargs['commits'].append(revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +00001048 return git_cache.Mirror(url, **mirror_kwargs)
1049
John Budorick882c91e2018-07-12 22:11:41 +00001050 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -08001051 """Update a git mirror by fetching the latest commits from the remote,
1052 unless mirror already contains revision whose type is sha1 hash.
1053 """
John Budorick882c91e2018-07-12 22:11:41 +00001054 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -08001055 if options.verbose:
1056 self.Print('skipping mirror update, it has rev=%s already' % revision,
1057 timestamp=False)
1058 return
1059
szager@chromium.org3ec84f62014-08-22 21:00:22 +00001060 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001061 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +00001062 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001063 depth = 10
1064 else:
1065 depth = 10000
1066 else:
1067 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00001068 mirror.populate(verbose=options.verbose,
1069 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +00001070 depth=depth,
Vadim Shtayura08049e22017-10-11 00:14:52 +00001071 lock_timeout=getattr(options, 'lock_timeout', 0))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001072
John Budorick882c91e2018-07-12 22:11:41 +00001073 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001074 """Clone a git repository from the given URL.
1075
msb@chromium.org786fb682010-06-02 15:16:23 +00001076 Once we've cloned the repo, we checkout a working branch if the specified
1077 revision is a branch head. If it is a tag or a specific commit, then we
1078 leave HEAD detached as it makes future updates simpler -- in this case the
1079 user should first create a new branch or switch to an existing branch before
1080 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001081 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001082 # git clone doesn't seem to insert a newline properly before printing
1083 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001084 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +00001085 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +00001086 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001087 if self.cache_dir:
1088 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001089 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001090 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001091 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +00001092 # If the parent directory does not exist, Git clone on Windows will not
1093 # create it, so we need to do it manually.
1094 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001095 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001096
1097 template_dir = None
1098 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +00001099 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001100 # In the case of a subproject, the pinned sha is not necessarily the
1101 # head of the remote branch (so we can't just use --depth=N). Instead,
1102 # we tell git to fetch all the remote objects from SHA..HEAD by means of
1103 # a template git dir which has a 'shallow' file pointing to the sha.
1104 template_dir = tempfile.mkdtemp(
1105 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
1106 dir=parent_dir)
1107 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
1108 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
1109 template_file.write(revision)
1110 clone_cmd.append('--template=' + template_dir)
1111 else:
1112 # Otherwise, we're just interested in the HEAD. Just use --depth.
1113 clone_cmd.append('--depth=1')
1114
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001115 tmp_dir = tempfile.mkdtemp(
1116 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
1117 dir=parent_dir)
1118 try:
1119 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001120 if self.print_outbuf:
1121 print_stdout = True
Edward Lemur24146be2019-08-01 21:44:52 +00001122 filter_fn = None
Edward Lemur231f5ea2018-01-31 19:02:36 +01001123 else:
1124 print_stdout = False
Edward Lemur24146be2019-08-01 21:44:52 +00001125 filter_fn = self.filter
Edward Lemur231f5ea2018-01-31 19:02:36 +01001126 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
Edward Lemur24146be2019-08-01 21:44:52 +00001127 print_stdout=print_stdout, filter_fn=filter_fn)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001128 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +00001129 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
1130 os.path.join(self.checkout_path, '.git'))
Edward Lesmesd4e20f22020-07-15 21:11:08 +00001131 # TODO(https://github.com/git-for-windows/git/issues/2569): Remove once
1132 # fixed.
1133 if sys.platform.startswith('win'):
1134 try:
1135 self._Run(['config', '--unset', 'core.worktree'], options,
1136 cwd=self.checkout_path)
1137 except subprocess2.CalledProcessError:
1138 pass
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001139 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001140 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001141 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001142 finally:
1143 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001144 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001145 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001146 if template_dir:
1147 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +00001148 self._SetFetchConfig(options)
1149 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001150 revision = self._AutoFetchRef(options, revision)
1151 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1152 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +00001153 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +00001154 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001155 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +00001156 ('Checked out %s to a detached HEAD. Before making any commits\n'
1157 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1158 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +00001159 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001160
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001161 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001162 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001163 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001164 raise gclient_utils.Error("Background task requires input. Rerun "
1165 "gclient with --jobs=1 so that\n"
1166 "interaction is possible.")
Edward Lesmesae3586b2020-03-23 21:21:14 +00001167 return gclient_utils.AskForData(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001168
1169
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001170 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001171 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001172 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001173 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001174 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001175 revision = upstream
1176 if newbase:
1177 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001178 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001179 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001180 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001181 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001182 printed_path = True
1183 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001184 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001185
1186 if merge:
1187 merge_output = self._Capture(['merge', revision])
1188 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001189 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001190 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001191
1192 # Build the rebase command here using the args
1193 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1194 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001195 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001196 rebase_cmd.append('--verbose')
1197 if newbase:
1198 rebase_cmd.extend(['--onto', newbase])
1199 rebase_cmd.append(upstream)
1200 if branch:
1201 rebase_cmd.append(branch)
1202
1203 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001204 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
Raul Tambreb946b232019-03-26 14:48:46 +00001205 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001206 if (re.match(br'cannot rebase: you have unstaged changes', e.stderr) or
1207 re.match(br'cannot rebase: your index contains uncommitted changes',
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001208 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001209 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001210 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001211 'Cannot rebase because of unstaged changes.\n'
1212 '\'git reset --hard HEAD\' ?\n'
1213 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001214 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001215 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001216 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001217 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001218 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001219 break
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00001220
1221 if re.match(r'quit|q', rebase_action, re.I):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001222 raise gclient_utils.Error("Please merge or rebase manually\n"
1223 "cd %s && git " % self.checkout_path
1224 + "%s" % ' '.join(rebase_cmd))
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00001225
1226 if re.match(r'show|s', rebase_action, re.I):
Edward Lemur979fa782019-08-13 22:44:05 +00001227 self.Print('%s' % e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001228 continue
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00001229
1230 gclient_utils.Error("Input not recognized")
1231 continue
Edward Lemur979fa782019-08-13 22:44:05 +00001232 elif re.search(br'^CONFLICT', e.stdout, re.M):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001233 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1234 "Fix the conflict and run gclient again.\n"
1235 "See 'man git-rebase' for details.\n")
1236 else:
Edward Lemur979fa782019-08-13 22:44:05 +00001237 self.Print(e.stdout.decode('utf-8').strip())
1238 self.Print('Rebase produced error output:\n%s' %
1239 e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001240 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1241 "manually.\ncd %s && git " %
1242 self.checkout_path
1243 + "%s" % ' '.join(rebase_cmd))
1244
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001245 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001246 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001247 # Make the output a little prettier. It's nice to have some
1248 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001249 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001250
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001251 @staticmethod
1252 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001253 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1254 if not ok:
1255 raise gclient_utils.Error('git version %s < minimum required %s' %
1256 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001257
John Budorick882c91e2018-07-12 22:11:41 +00001258 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001259 # Special case handling if all 3 conditions are met:
1260 # * the mirros have recently changed, but deps destination remains same,
1261 # * the git histories of mirrors are conflicting.
1262 # * git cache is used
1263 # This manifests itself in current checkout having invalid HEAD commit on
1264 # most git operations. Since git cache is used, just deleted the .git
1265 # folder, and re-create it by cloning.
1266 try:
1267 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1268 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001269 if (b'fatal: bad object HEAD' in e.stderr
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001270 and self.cache_dir and self.cache_dir in url):
1271 self.Print((
1272 'Likely due to DEPS change with git cache_dir, '
1273 'the current commit points to no longer existing object.\n'
1274 '%s' % e)
1275 )
1276 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001277 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001278 else:
1279 raise
1280
msb@chromium.org786fb682010-06-02 15:16:23 +00001281 def _IsRebasing(self):
1282 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1283 # have a plumbing command to determine whether a rebase is in progress, so
1284 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1285 g = os.path.join(self.checkout_path, '.git')
1286 return (
1287 os.path.isdir(os.path.join(g, "rebase-merge")) or
1288 os.path.isdir(os.path.join(g, "rebase-apply")))
1289
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001290 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001291 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1292 if os.path.exists(lockfile):
1293 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001294 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001295 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1296 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001297 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001298
msb@chromium.org786fb682010-06-02 15:16:23 +00001299 # Make sure the tree is clean; see git-rebase.sh for reference
1300 try:
1301 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001302 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001303 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001304 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001305 '\tYou have unstaged changes.\n'
1306 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001307 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001308 try:
1309 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001310 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001311 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001312 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001313 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001314 '\tYour index contains uncommitted changes\n'
1315 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001316 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001317
agable83faed02016-10-24 14:37:10 -07001318 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001319 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1320 # reference by a commit). If not, error out -- most likely a rebase is
1321 # in progress, try to detect so we can give a better error.
1322 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001323 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1324 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001325 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001326 # Commit is not contained by any rev. See if the user is rebasing:
1327 if self._IsRebasing():
1328 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001329 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001330 '\tAlready in a conflict, i.e. (no branch).\n'
1331 '\tFix the conflict and run gclient again.\n'
1332 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1333 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001334 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001335 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001336 name = ('saved-by-gclient-' +
1337 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001338 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001339 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001340 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001341
msb@chromium.org5bde4852009-12-14 16:47:12 +00001342 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001343 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001344 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001345 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001346 return None
1347 return branch
1348
borenet@google.comc3e09d22014-04-10 13:58:18 +00001349 def _Capture(self, args, **kwargs):
Mike Frysinger286fb162019-09-30 03:14:10 +00001350 set_git_dir = 'cwd' not in kwargs
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001351 kwargs.setdefault('cwd', self.checkout_path)
1352 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001353 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001354 env = scm.GIT.ApplyEnvVars(kwargs)
Mike Frysinger286fb162019-09-30 03:14:10 +00001355 # If an explicit cwd isn't set, then default to the .git/ subdir so we get
1356 # stricter behavior. This can be useful in cases of slight corruption --
1357 # we don't accidentally go corrupting parent git checks too. See
1358 # https://crbug.com/1000825 for an example.
1359 if set_git_dir:
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001360 git_dir = os.path.abspath(os.path.join(self.checkout_path, '.git'))
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001361 # Depending on how the .gclient file was defined, self.checkout_path
1362 # might be set to a unicode string, not a regular string; on Windows
1363 # Python2, we can't set env vars to be unicode strings, so we
1364 # forcibly cast the value to a string before setting it.
1365 env.setdefault('GIT_DIR', str(git_dir))
Raul Tambrecd862e32019-05-10 21:19:00 +00001366 ret = subprocess2.check_output(
1367 ['git'] + args, env=env, **kwargs).decode('utf-8')
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001368 if strip:
1369 ret = ret.strip()
Erik Chene16ffff2019-10-14 20:35:53 +00001370 self.Print('Finished running: %s %s' % ('git', ' '.join(args)))
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001371 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001372
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001373 def _Checkout(self, options, ref, force=False, quiet=None):
1374 """Performs a 'git-checkout' operation.
1375
1376 Args:
1377 options: The configured option set
1378 ref: (str) The branch/commit to checkout
Quinten Yearsley925cedb2020-04-13 17:49:39 +00001379 quiet: (bool/None) Whether or not the checkout should pass '--quiet'; if
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001380 'None', the behavior is inferred from 'options.verbose'.
1381 Returns: (str) The output of the checkout operation
1382 """
1383 if quiet is None:
1384 quiet = (not options.verbose)
1385 checkout_args = ['checkout']
1386 if force:
1387 checkout_args.append('--force')
1388 if quiet:
1389 checkout_args.append('--quiet')
1390 checkout_args.append(ref)
1391 return self._Capture(checkout_args)
1392
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001393 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1394 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001395 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemur9a5e3bd2019-04-02 23:37:45 +00001396 # When updating, the ref is modified to be a remote ref .
1397 # (e.g. refs/heads/NAME becomes refs/remotes/REMOTE/NAME).
1398 # Try to reverse that mapping.
1399 original_ref = scm.GIT.RemoteRefToRef(refspec, self.remote)
1400 if original_ref:
1401 refspec = original_ref + ':' + refspec
1402 # When a mirror is configured, it only fetches
1403 # refs/{heads,branch-heads,tags}/*.
1404 # If asked to fetch other refs, we must fetch those directly from the
1405 # repository, and not from the mirror.
1406 if not original_ref.startswith(
1407 ('refs/heads/', 'refs/branch-heads/', 'refs/tags/')):
1408 remote, _ = gclient_utils.SplitUrlRevision(self.url)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001409 fetch_cmd = cfg + [
1410 'fetch',
1411 remote or self.remote,
1412 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001413 if refspec:
1414 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001415
1416 if prune:
1417 fetch_cmd.append('--prune')
1418 if options.verbose:
1419 fetch_cmd.append('--verbose')
danakjd5c0b562019-11-08 17:27:47 +00001420 if not hasattr(options, 'with_tags') or not options.with_tags:
1421 fetch_cmd.append('--no-tags')
dnj@chromium.org680f2172014-06-25 00:39:32 +00001422 elif quiet:
1423 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001424 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001425
Edward Lemur579c9862018-07-13 23:17:51 +00001426 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001427 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1428 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001429 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001430 try:
1431 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1432 options)
1433 self._Run(['config', 'remote.%s.fetch' % self.remote,
1434 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1435 except subprocess2.CalledProcessError as e:
1436 # If exit code was 5, it means we attempted to unset a config that
1437 # didn't exist. Ignore it.
1438 if e.returncode != 5:
1439 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001440 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001441 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001442 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1443 '^\\+refs/branch-heads/\\*:.*$']
1444 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001445 if hasattr(options, 'with_tags') and options.with_tags:
1446 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1447 '+refs/tags/*:refs/tags/*',
1448 '^\\+refs/tags/\\*:.*$']
1449 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001450
John Budorick882c91e2018-07-12 22:11:41 +00001451 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001452 """Attempts to fetch |revision| if not available in local repo.
1453
1454 Returns possibly updated revision."""
Edward Lemure0ba7b82020-03-11 20:31:32 +00001455 if not scm.GIT.IsValidRevision(self.checkout_path, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001456 self._Fetch(options, refspec=revision)
1457 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1458 return revision
1459
Edward Lemur24146be2019-08-01 21:44:52 +00001460 def _Run(self, args, options, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001461 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001462 kwargs.setdefault('cwd', self.checkout_path)
Edward Lemur24146be2019-08-01 21:44:52 +00001463 kwargs.setdefault('filter_fn', self.filter)
1464 kwargs.setdefault('show_header', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001465 env = scm.GIT.ApplyEnvVars(kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +00001466
agable@chromium.org772efaf2014-04-01 02:35:44 +00001467 cmd = ['git'] + args
Edward Lemur24146be2019-08-01 21:44:52 +00001468 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001469
1470
1471class CipdPackage(object):
1472 """A representation of a single CIPD package."""
1473
John Budorickd3ba72b2018-03-20 12:27:42 -07001474 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001475 self._authority_for_subdir = authority_for_subdir
1476 self._name = name
1477 self._version = version
1478
1479 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001480 def authority_for_subdir(self):
1481 """Whether this package has authority to act on behalf of its subdir.
1482
1483 Some operations should only be performed once per subdirectory. A package
1484 that has authority for its subdirectory is the only package that should
1485 perform such operations.
1486
1487 Returns:
1488 bool; whether this package has subdir authority.
1489 """
1490 return self._authority_for_subdir
1491
1492 @property
1493 def name(self):
1494 return self._name
1495
1496 @property
1497 def version(self):
1498 return self._version
1499
1500
1501class CipdRoot(object):
1502 """A representation of a single CIPD root."""
1503 def __init__(self, root_dir, service_url):
1504 self._all_packages = set()
1505 self._mutator_lock = threading.Lock()
1506 self._packages_by_subdir = collections.defaultdict(list)
1507 self._root_dir = root_dir
1508 self._service_url = service_url
1509
1510 def add_package(self, subdir, package, version):
1511 """Adds a package to this CIPD root.
1512
1513 As far as clients are concerned, this grants both root and subdir authority
1514 to packages arbitrarily. (The implementation grants root authority to the
1515 first package added and subdir authority to the first package added for that
1516 subdir, but clients should not depend on or expect that behavior.)
1517
1518 Args:
1519 subdir: str; relative path to where the package should be installed from
1520 the cipd root directory.
1521 package: str; the cipd package name.
1522 version: str; the cipd package version.
1523 Returns:
1524 CipdPackage; the package that was created and added to this root.
1525 """
1526 with self._mutator_lock:
1527 cipd_package = CipdPackage(
1528 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001529 not self._packages_by_subdir[subdir])
1530 self._all_packages.add(cipd_package)
1531 self._packages_by_subdir[subdir].append(cipd_package)
1532 return cipd_package
1533
1534 def packages(self, subdir):
1535 """Get the list of configured packages for the given subdir."""
1536 return list(self._packages_by_subdir[subdir])
1537
1538 def clobber(self):
1539 """Remove the .cipd directory.
1540
1541 This is useful for forcing ensure to redownload and reinitialize all
1542 packages.
1543 """
1544 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001545 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001546 try:
1547 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1548 except OSError:
1549 if os.path.exists(cipd_cache_dir):
1550 raise
1551
1552 @contextlib.contextmanager
1553 def _create_ensure_file(self):
1554 try:
Stephanie Kim700aee72022-06-01 19:58:30 +00001555 contents = '$ParanoidMode CheckPresence\n'
1556 # TODO(crbug/1329641): Remove once cipd packages have been updated
1557 # to always be created in copy mode.
1558 contents += '$OverrideInstallMode copy\n\n'
Edward Lesmes05934952019-12-19 20:38:09 +00001559 for subdir, packages in sorted(self._packages_by_subdir.items()):
1560 contents += '@Subdir %s\n' % subdir
1561 for package in sorted(packages, key=lambda p: p.name):
1562 contents += '%s %s\n' % (package.name, package.version)
1563 contents += '\n'
John Budorick0f7b2002018-01-19 15:46:17 -08001564 ensure_file = None
1565 with tempfile.NamedTemporaryFile(
Edward Lesmes05934952019-12-19 20:38:09 +00001566 suffix='.ensure', delete=False, mode='wb') as ensure_file:
1567 ensure_file.write(contents.encode('utf-8', 'replace'))
John Budorick0f7b2002018-01-19 15:46:17 -08001568 yield ensure_file.name
1569 finally:
1570 if ensure_file is not None and os.path.exists(ensure_file.name):
1571 os.remove(ensure_file.name)
1572
1573 def ensure(self):
1574 """Run `cipd ensure`."""
1575 with self._mutator_lock:
1576 with self._create_ensure_file() as ensure_file:
1577 cmd = [
1578 'cipd', 'ensure',
1579 '-log-level', 'error',
1580 '-root', self.root_dir,
1581 '-ensure-file', ensure_file,
1582 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001583 gclient_utils.CheckCallAndFilter(
1584 cmd, print_stdout=True, show_header=True)
John Budorick0f7b2002018-01-19 15:46:17 -08001585
John Budorickd3ba72b2018-03-20 12:27:42 -07001586 def run(self, command):
1587 if command == 'update':
1588 self.ensure()
1589 elif command == 'revert':
1590 self.clobber()
1591 self.ensure()
1592
John Budorick0f7b2002018-01-19 15:46:17 -08001593 def created_package(self, package):
1594 """Checks whether this root created the given package.
1595
1596 Args:
1597 package: CipdPackage; the package to check.
1598 Returns:
1599 bool; whether this root created the given package.
1600 """
1601 return package in self._all_packages
1602
1603 @property
1604 def root_dir(self):
1605 return self._root_dir
1606
1607 @property
1608 def service_url(self):
1609 return self._service_url
1610
1611
1612class CipdWrapper(SCMWrapper):
1613 """Wrapper for CIPD.
1614
1615 Currently only supports chrome-infra-packages.appspot.com.
1616 """
John Budorick3929e9e2018-02-04 18:18:07 -08001617 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001618
1619 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1620 out_cb=None, root=None, package=None):
1621 super(CipdWrapper, self).__init__(
1622 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1623 out_cb=out_cb)
1624 assert root.created_package(package)
1625 self._package = package
1626 self._root = root
1627
1628 #override
1629 def GetCacheMirror(self):
1630 return None
1631
1632 #override
1633 def GetActualRemoteURL(self, options):
1634 return self._root.service_url
1635
1636 #override
1637 def DoesRemoteURLMatch(self, options):
1638 del options
1639 return True
1640
1641 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001642 """Does nothing.
1643
1644 CIPD packages should be reverted at the root by running
1645 `CipdRoot.run('revert')`.
1646 """
John Budorick0f7b2002018-01-19 15:46:17 -08001647
1648 def diff(self, options, args, file_list):
1649 """CIPD has no notion of diffing."""
John Budorick0f7b2002018-01-19 15:46:17 -08001650
1651 def pack(self, options, args, file_list):
1652 """CIPD has no notion of diffing."""
John Budorick0f7b2002018-01-19 15:46:17 -08001653
1654 def revinfo(self, options, args, file_list):
1655 """Grab the instance ID."""
1656 try:
1657 tmpdir = tempfile.mkdtemp()
1658 describe_json_path = os.path.join(tmpdir, 'describe.json')
1659 cmd = [
1660 'cipd', 'describe',
1661 self._package.name,
1662 '-log-level', 'error',
1663 '-version', self._package.version,
1664 '-json-output', describe_json_path
1665 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001666 gclient_utils.CheckCallAndFilter(cmd)
John Budorick0f7b2002018-01-19 15:46:17 -08001667 with open(describe_json_path) as f:
1668 describe_json = json.load(f)
1669 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1670 finally:
1671 gclient_utils.rmtree(tmpdir)
1672
1673 def status(self, options, args, file_list):
1674 pass
1675
1676 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001677 """Does nothing.
1678
1679 CIPD packages should be updated at the root by running
1680 `CipdRoot.run('update')`.
1681 """