blob: 41e8e926a3f3dd616bedee16f1354f68134e977a [file] [log] [blame]
steveblock@chromium.org93567042012-02-15 01:02:26 +00001# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00004
maruel@chromium.orgd5800f12009-11-12 20:03:43 +00005"""Gclient-specific SCM-specific operations."""
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00006
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00007from __future__ import print_function
8
John Budorick0f7b2002018-01-19 15:46:17 -08009import collections
10import contextlib
borenet@google.comb2256212014-05-07 20:57:28 +000011import errno
John Budorick0f7b2002018-01-19 15:46:17 -080012import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000013import logging
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000014import os
Tomasz Wiszkowskid4e66882021-08-19 21:35:09 +000015import platform
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000016import posixpath
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000017import re
maruel@chromium.org90541732011-04-01 17:54:18 +000018import sys
ilevy@chromium.org3534aa52013-07-20 01:58:08 +000019import tempfile
John Budorick0f7b2002018-01-19 15:46:17 -080020import threading
zty@chromium.org6279e8a2014-02-13 01:45:25 +000021import traceback
Raul Tambreb946b232019-03-26 14:48:46 +000022
23try:
24 import urlparse
25except ImportError: # For Py3 compatibility
26 import urllib.parse as urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000027
28import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +000029import git_cache
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000030import scm
borenet@google.comb2256212014-05-07 20:57:28 +000031import shutil
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000032import subprocess2
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000033
34
smutae7ea312016-07-18 11:59:41 -070035class NoUsableRevError(gclient_utils.Error):
36 """Raised if requested revision isn't found in checkout."""
37
38
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000039class DiffFiltererWrapper(object):
40 """Simple base class which tracks which file is being diffed and
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000041 replaces instances of its file name in the original and
agable41e3a6c2016-10-20 11:36:56 -070042 working copy lines of the git diff output."""
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000043 index_string = None
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000044 original_prefix = "--- "
45 working_prefix = "+++ "
46
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000047 def __init__(self, relpath, print_func):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000048 # Note that we always use '/' as the path separator to be
agable41e3a6c2016-10-20 11:36:56 -070049 # consistent with cygwin-style output on Windows
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000050 self._relpath = relpath.replace("\\", "/")
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000051 self._current_file = None
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000052 self._print_func = print_func
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000053
maruel@chromium.org6e29d572010-06-04 17:32:20 +000054 def SetCurrentFile(self, current_file):
55 self._current_file = current_file
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000056
iannucci@chromium.org3830a672013-02-19 20:15:14 +000057 @property
58 def _replacement_file(self):
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000059 return posixpath.join(self._relpath, self._current_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000060
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000061 def _Replace(self, line):
62 return line.replace(self._current_file, self._replacement_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000063
64 def Filter(self, line):
65 if (line.startswith(self.index_string)):
66 self.SetCurrentFile(line[len(self.index_string):])
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000067 line = self._Replace(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000068 else:
69 if (line.startswith(self.original_prefix) or
70 line.startswith(self.working_prefix)):
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000071 line = self._Replace(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000072 self._print_func(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000073
74
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000075class GitDiffFilterer(DiffFiltererWrapper):
76 index_string = "diff --git "
77
78 def SetCurrentFile(self, current_file):
79 # Get filename by parsing "a/<filename> b/<filename>"
80 self._current_file = current_file[:(len(current_file)/2)][2:]
81
82 def _Replace(self, line):
83 return re.sub("[a|b]/" + self._current_file, self._replacement_file, line)
84
85
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000086# SCMWrapper base class
87
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000088class SCMWrapper(object):
89 """Add necessary glue between all the supported SCM.
90
msb@chromium.orgd6504212010-01-13 17:34:31 +000091 This is the abstraction layer to bind to different SCM.
92 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000093 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
Edward Lemur231f5ea2018-01-31 19:02:36 +010094 out_cb=None, print_outbuf=False):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000095 self.url = url
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +000096 self._root_dir = root_dir
97 if self._root_dir:
98 self._root_dir = self._root_dir.replace('/', os.sep)
99 self.relpath = relpath
100 if self.relpath:
101 self.relpath = self.relpath.replace('/', os.sep)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000102 if self.relpath and self._root_dir:
103 self.checkout_path = os.path.join(self._root_dir, self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000104 if out_fh is None:
105 out_fh = sys.stdout
106 self.out_fh = out_fh
107 self.out_cb = out_cb
Edward Lemur231f5ea2018-01-31 19:02:36 +0100108 self.print_outbuf = print_outbuf
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000109
110 def Print(self, *args, **kwargs):
111 kwargs.setdefault('file', self.out_fh)
112 if kwargs.pop('timestamp', True):
113 self.out_fh.write('[%s] ' % gclient_utils.Elapsed())
114 print(*args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000115
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000116 def RunCommand(self, command, options, args, file_list=None):
agabledebf6c82016-12-21 12:50:12 -0800117 commands = ['update', 'updatesingle', 'revert',
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000118 'revinfo', 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000119
120 if not command in commands:
121 raise gclient_utils.Error('Unknown command %s' % command)
122
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000123 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000124 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000125 command, self.__class__.__name__))
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000126
127 return getattr(self, command)(options, args, file_list)
128
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000129 @staticmethod
130 def _get_first_remote_url(checkout_path):
131 log = scm.GIT.Capture(
132 ['config', '--local', '--get-regexp', r'remote.*.url'],
133 cwd=checkout_path)
134 # Get the second token of the first line of the log.
135 return log.splitlines()[0].split(' ', 1)[1]
136
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000137 def GetCacheMirror(self):
Robert Iannuccia19649b2018-06-29 16:31:45 +0000138 if getattr(self, 'cache_dir', None):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000139 url, _ = gclient_utils.SplitUrlRevision(self.url)
140 return git_cache.Mirror(url)
141 return None
142
smut@google.comd33eab32014-07-07 19:35:18 +0000143 def GetActualRemoteURL(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000144 """Attempt to determine the remote URL for this SCMWrapper."""
smut@google.comd33eab32014-07-07 19:35:18 +0000145 # Git
borenet@google.combda475e2014-03-24 19:04:45 +0000146 if os.path.exists(os.path.join(self.checkout_path, '.git')):
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000147 actual_remote_url = self._get_first_remote_url(self.checkout_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000148
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000149 mirror = self.GetCacheMirror()
150 # If the cache is used, obtain the actual remote URL from there.
151 if (mirror and mirror.exists() and
152 mirror.mirror_path.replace('\\', '/') ==
153 actual_remote_url.replace('\\', '/')):
154 actual_remote_url = self._get_first_remote_url(mirror.mirror_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000155 return actual_remote_url
borenet@google.com88d10082014-03-21 17:24:48 +0000156 return None
157
borenet@google.com4e9be262014-04-08 19:40:30 +0000158 def DoesRemoteURLMatch(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000159 """Determine whether the remote URL of this checkout is the expected URL."""
160 if not os.path.exists(self.checkout_path):
161 # A checkout which doesn't exist can't be broken.
162 return True
163
smut@google.comd33eab32014-07-07 19:35:18 +0000164 actual_remote_url = self.GetActualRemoteURL(options)
borenet@google.com88d10082014-03-21 17:24:48 +0000165 if actual_remote_url:
borenet@google.com8156c9f2014-04-01 16:41:36 +0000166 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
167 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
Aravind Vasudevan22bf6052022-01-24 21:11:19 +0000168
169 # This may occur if the self.checkout_path exists but does not contain a
170 # valid git checkout.
171 return False
borenet@google.com88d10082014-03-21 17:24:48 +0000172
borenet@google.comb09097a2014-04-09 19:09:08 +0000173 def _DeleteOrMove(self, force):
174 """Delete the checkout directory or move it out of the way.
175
176 Args:
177 force: bool; if True, delete the directory. Otherwise, just move it.
178 """
borenet@google.comb2256212014-05-07 20:57:28 +0000179 if force and os.environ.get('CHROME_HEADLESS') == '1':
180 self.Print('_____ Conflicting directory found in %s. Removing.'
181 % self.checkout_path)
182 gclient_utils.AddWarning('Conflicting directory %s deleted.'
183 % self.checkout_path)
184 gclient_utils.rmtree(self.checkout_path)
185 else:
186 bad_scm_dir = os.path.join(self._root_dir, '_bad_scm',
187 os.path.dirname(self.relpath))
188
189 try:
190 os.makedirs(bad_scm_dir)
191 except OSError as e:
192 if e.errno != errno.EEXIST:
193 raise
194
195 dest_path = tempfile.mkdtemp(
196 prefix=os.path.basename(self.relpath),
197 dir=bad_scm_dir)
198 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
199 % (self.checkout_path, dest_path))
200 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
201 % (self.checkout_path, dest_path))
202 shutil.move(self.checkout_path, dest_path)
borenet@google.comb09097a2014-04-09 19:09:08 +0000203
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000204
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000205class GitWrapper(SCMWrapper):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000206 """Wrapper for Git"""
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000207 name = 'git'
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000208 remote = 'origin'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000209
Robert Iannuccia19649b2018-06-29 16:31:45 +0000210 @property
211 def cache_dir(self):
212 try:
213 return git_cache.Mirror.GetCachePath()
214 except RuntimeError:
215 return None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000216
John Budorick0f7b2002018-01-19 15:46:17 -0800217 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000218 """Removes 'git+' fake prefix from git URL."""
Henrique Ferreiroe72279d2019-04-17 12:01:50 +0000219 if url and (url.startswith('git+http://') or
220 url.startswith('git+https://')):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000221 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800222 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000223 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
224 if self.out_cb:
225 filter_kwargs['predicate'] = self.out_cb
226 self.filter = gclient_utils.GitFilter(**filter_kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +0000227 self._running_under_rosetta = None
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000228
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000229 def GetCheckoutRoot(self):
230 return scm.GIT.GetCheckoutRoot(self.checkout_path)
231
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000232 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000233 """Returns the given revision's date in ISO-8601 format (which contains the
234 time zone)."""
235 # TODO(floitsch): get the time-stamp of the given revision and not just the
236 # time-stamp of the currently checked out revision.
237 return self._Capture(['log', '-n', '1', '--format=%ai'])
238
Aaron Gablef4068aa2017-12-12 15:14:09 -0800239 def _GetDiffFilenames(self, base):
240 """Returns the names of files modified since base."""
241 return self._Capture(
Raul Tambrecd862e32019-05-10 21:19:00 +0000242 # Filter to remove base if it is None.
243 list(filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only',
244 base])
245 )).split()
Aaron Gablef4068aa2017-12-12 15:14:09 -0800246
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000247 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 15:45:56 -0800248 _, revision = gclient_utils.SplitUrlRevision(self.url)
249 if not revision:
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000250 revision = 'refs/remotes/%s/main' % self.remote
Aaron Gable1853f662018-02-12 15:45:56 -0800251 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000252
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000253 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000254 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000255 repository.
256
257 The patch file is generated from a diff of the merge base of HEAD and
258 its upstream branch.
259 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700260 try:
261 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
262 except subprocess2.CalledProcessError:
263 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000264 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700265 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000266 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000267 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000268
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800269 def _Scrub(self, target, options):
270 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000271 quiet = []
272 if not options.verbose:
273 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800274 self._Run(['reset', '--hard', target] + quiet, options)
275 if options.force and options.delete_unversioned_trees:
276 # where `target` is a commit that contains both upper and lower case
277 # versions of the same file on a case insensitive filesystem, we are
278 # actually in a broken state here. The index will have both 'a' and 'A',
279 # but only one of them will exist on the disk. To progress, we delete
280 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800281 output = self._Capture([
282 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800283 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800284 # --porcelain (v1) looks like:
285 # XY filename
286 try:
287 filename = line[3:]
288 self.Print('_____ Deleting residual after reset: %r.' % filename)
289 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800290 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800291 except OSError:
292 pass
293
John Budorick882c91e2018-07-12 22:11:41 +0000294 def _FetchAndReset(self, revision, file_list, options):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800295 """Equivalent to git fetch; git reset."""
Edward Lemur579c9862018-07-13 23:17:51 +0000296 self._SetFetchConfig(options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000297
dnj@chromium.org680f2172014-06-25 00:39:32 +0000298 self._Fetch(options, prune=True, quiet=options.verbose)
John Budorick882c91e2018-07-12 22:11:41 +0000299 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000300 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800301 files = self._Capture(
302 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambreb946b232019-03-26 14:48:46 +0000303 file_list.extend(
Edward Lemur26a8b9f2019-08-15 20:46:44 +0000304 [os.path.join(self.checkout_path, f) for f in files])
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000305
szager@chromium.org8a139702014-06-20 15:55:01 +0000306 def _DisableHooks(self):
307 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
308 if not os.path.isdir(hook_dir):
309 return
310 for f in os.listdir(hook_dir):
311 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000312 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
313 if os.path.exists(disabled_hook_path):
314 os.remove(disabled_hook_path)
315 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000316
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000317 def _maybe_break_locks(self, options):
318 """This removes all .lock files from this repo's .git directory, if the
319 user passed the --break_repo_locks command line flag.
320
321 In particular, this will cleanup index.lock files, as well as ref lock
322 files.
323 """
324 if options.break_repo_locks:
325 git_dir = os.path.join(self.checkout_path, '.git')
326 for path, _, filenames in os.walk(git_dir):
327 for filename in filenames:
328 if filename.endswith('.lock'):
329 to_break = os.path.join(path, filename)
330 self.Print('breaking lock: %s' % (to_break,))
331 try:
332 os.remove(to_break)
333 except OSError as ex:
334 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
335 raise
336
Edward Lemur3acbc742019-05-30 17:57:35 +0000337 def apply_patch_ref(self, patch_repo, patch_rev, target_rev, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000338 file_list):
339 """Apply a patch on top of the revision we're synced at.
340
Edward Lemur3acbc742019-05-30 17:57:35 +0000341 The patch ref is given by |patch_repo|@|patch_rev|.
342 |target_rev| is usually the branch that the |patch_rev| was uploaded against
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000343 (e.g. 'refs/heads/main'), but this is not required.
Edward Lemur3acbc742019-05-30 17:57:35 +0000344
345 We cherry-pick all commits reachable from |patch_rev| on top of the curret
346 HEAD, excluding those reachable from |target_rev|
347 (i.e. git cherry-pick target_rev..patch_rev).
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000348
349 Graphically, it looks like this:
350
Edward Lemur3acbc742019-05-30 17:57:35 +0000351 ... -> o -> [possibly already landed commits] -> target_rev
352 \
353 -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000354
Edward Lemur3acbc742019-05-30 17:57:35 +0000355 The final checkout state is then:
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000356
Edward Lemur3acbc742019-05-30 17:57:35 +0000357 ... -> HEAD -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000358
359 After application, if |options.reset_patch_ref| is specified, we soft reset
Edward Lemur3acbc742019-05-30 17:57:35 +0000360 the cherry-picked changes, keeping them in git index only.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000361
362 Args:
Edward Lemur3acbc742019-05-30 17:57:35 +0000363 patch_repo: The patch origin.
364 e.g. 'https://foo.googlesource.com/bar'
365 patch_rev: The revision to patch.
366 e.g. 'refs/changes/1234/34/1'.
367 target_rev: The revision to use when finding the merge base.
368 Typically, the branch that the patch was uploaded against.
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000369 e.g. 'refs/heads/main' or 'refs/heads/infra/config'.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000370 options: The options passed to gclient.
371 file_list: A list where modified files will be appended.
372 """
373
Edward Lemurca7d8812018-07-24 17:42:45 +0000374 # Abort any cherry-picks in progress.
375 try:
376 self._Capture(['cherry-pick', '--abort'])
377 except subprocess2.CalledProcessError:
378 pass
379
Edward Lesmesc621b212018-03-21 20:26:56 -0400380 base_rev = self._Capture(['rev-parse', 'HEAD'])
Edward Lemura0ffbe42019-05-01 16:52:18 +0000381
Edward Lemur3acbc742019-05-30 17:57:35 +0000382 if not target_rev:
Edward Lemur4c5c8ab2019-06-07 15:58:13 +0000383 raise gclient_utils.Error('A target revision for the patch must be given')
Aravind Vasudevan22bf6052022-01-24 21:11:19 +0000384
385 if target_rev.startswith(('refs/heads/', 'refs/branch-heads')):
Edward Lesmesf627d9f2020-07-23 19:50:50 +0000386 # If |target_rev| is in refs/heads/** or refs/branch-heads/**, try first
387 # to find the corresponding remote ref for it, since |target_rev| might
388 # point to a local ref which is not up to date with the corresponding
389 # remote ref.
Edward Lemur3acbc742019-05-30 17:57:35 +0000390 remote_ref = ''.join(scm.GIT.RefToRemoteRef(target_rev, self.remote))
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000391 self.Print('Trying the corresponding remote ref for %r: %r\n' % (
Edward Lemur3acbc742019-05-30 17:57:35 +0000392 target_rev, remote_ref))
393 if scm.GIT.IsValidRevision(self.checkout_path, remote_ref):
Josip Sokcevic3d7cbce2021-10-05 20:48:04 +0000394 # refs/remotes may need to be updated to cleanly cherry-pick changes.
395 # See https://crbug.com/1255178.
396 self._Capture(['fetch', '--no-tags', self.remote, target_rev])
Edward Lemur3acbc742019-05-30 17:57:35 +0000397 target_rev = remote_ref
398 elif not scm.GIT.IsValidRevision(self.checkout_path, target_rev):
399 # Fetch |target_rev| if it's not already available.
400 url, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmes07a68342021-04-20 23:39:30 +0000401 mirror = self._GetMirror(url, options, target_rev, target_rev)
Edward Lemur3acbc742019-05-30 17:57:35 +0000402 if mirror:
403 rev_type = 'branch' if target_rev.startswith('refs/') else 'hash'
404 self._UpdateMirrorIfNotContains(mirror, options, rev_type, target_rev)
405 self._Fetch(options, refspec=target_rev)
Edward Lemura0ffbe42019-05-01 16:52:18 +0000406
Edward Lemur3acbc742019-05-30 17:57:35 +0000407 self.Print('===Applying patch===')
408 self.Print('Revision to patch is %r @ %r.' % (patch_repo, patch_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000409 self.Print('Current dir is %r' % self.checkout_path)
Edward Lesmesc621b212018-03-21 20:26:56 -0400410 self._Capture(['reset', '--hard'])
danakjd5c0b562019-11-08 17:27:47 +0000411 self._Capture(['fetch', '--no-tags', patch_repo, patch_rev])
Edward Lemurca7d8812018-07-24 17:42:45 +0000412 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400413
Edward Lemur3acbc742019-05-30 17:57:35 +0000414 if not options.rebase_patch_ref:
415 self._Capture(['checkout', patch_rev])
Robert Iannuccic39a7782019-11-01 18:30:33 +0000416 # Adjust base_rev to be the first parent of our checked out patch ref;
417 # This will allow us to correctly extend `file_list`, and will show the
418 # correct file-list to programs which do `git diff --cached` expecting to
419 # see the patch diff.
420 base_rev = self._Capture(['rev-parse', patch_rev+'~'])
421
Edward Lemur3acbc742019-05-30 17:57:35 +0000422 else:
Robert Iannuccic39a7782019-11-01 18:30:33 +0000423 self.Print('Will cherrypick %r .. %r on top of %r.' % (
424 target_rev, patch_rev, base_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000425 try:
426 if scm.GIT.IsAncestor(self.checkout_path, patch_rev, target_rev):
427 # If |patch_rev| is an ancestor of |target_rev|, check it out.
Edward Lemurca7d8812018-07-24 17:42:45 +0000428 self._Capture(['checkout', patch_rev])
429 else:
430 # If a change was uploaded on top of another change, which has already
431 # landed, one of the commits in the cherry-pick range will be
432 # redundant, since it has already landed and its changes incorporated
433 # in the tree.
434 # We pass '--keep-redundant-commits' to ignore those changes.
Edward Lemur3acbc742019-05-30 17:57:35 +0000435 self._Capture(['cherry-pick', target_rev + '..' + patch_rev,
Josip Sokcevic804165b2021-11-30 01:16:39 +0000436 '--keep-redundant-commits'])
Edward Lemurca7d8812018-07-24 17:42:45 +0000437
Edward Lemur3acbc742019-05-30 17:57:35 +0000438 except subprocess2.CalledProcessError as e:
439 self.Print('Failed to apply patch.')
440 self.Print('Revision to patch was %r @ %r.' % (patch_repo, patch_rev))
441 self.Print('Tried to cherrypick %r .. %r on top of %r.' % (
442 target_rev, patch_rev, base_rev))
443 self.Print('Current dir is %r' % self.checkout_path)
444 self.Print('git returned non-zero exit status %s:\n%s' % (
Edward Lemur979fa782019-08-13 22:44:05 +0000445 e.returncode, e.stderr.decode('utf-8')))
Edward Lemur3acbc742019-05-30 17:57:35 +0000446 # Print the current status so that developers know what changes caused
447 # the patch failure, since git cherry-pick doesn't show that
448 # information.
449 self.Print(self._Capture(['status']))
450 try:
451 self._Capture(['cherry-pick', '--abort'])
452 except subprocess2.CalledProcessError:
453 pass
454 raise
Edward Lemurca7d8812018-07-24 17:42:45 +0000455
Edward Lemur3acbc742019-05-30 17:57:35 +0000456 if file_list is not None:
457 file_list.extend(self._GetDiffFilenames(base_rev))
Edward Lemurca7d8812018-07-24 17:42:45 +0000458
Edward Lesmesc621b212018-03-21 20:26:56 -0400459 if options.reset_patch_ref:
460 self._Capture(['reset', '--soft', base_rev])
461
msb@chromium.orge28e4982009-09-25 20:51:45 +0000462 def update(self, options, args, file_list):
463 """Runs git to update or transparently checkout the working copy.
464
465 All updated files will be appended to file_list.
466
467 Raises:
468 Error: if can't get URL for relative path.
469 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000470 if args:
471 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
472
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000473 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000474
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000475 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000476 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000477 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000478 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000479 # Override the revision number.
480 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000481 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000482 # Check again for a revision in case an initial ref was specified
483 # in the url, for example bla.git@refs/heads/custombranch
484 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000485 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000486 if not revision:
Josip Sokcevic091f5ac2021-01-14 23:14:21 +0000487 # If a dependency is not pinned, track the default remote branch.
488 revision = scm.GIT.GetRemoteHeadRef(self.checkout_path, self.url,
489 self.remote)
Edward Lesmes4ea67bb2021-04-20 17:33:52 +0000490 if revision.startswith('origin/'):
491 revision = 'refs/remotes/' + revision
msb@chromium.orge28e4982009-09-25 20:51:45 +0000492
Tomasz Wiszkowskid4e66882021-08-19 21:35:09 +0000493 if managed and platform.system() == 'Windows':
szager@chromium.org8a139702014-06-20 15:55:01 +0000494 self._DisableHooks()
495
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000496 printed_path = False
497 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000498 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700499 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000500 verbose = ['--verbose']
501 printed_path = True
502
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000503 revision_ref = revision
504 if ':' in revision:
505 revision_ref, _, revision = revision.partition(':')
506
Edward Lesmes8073a502020-04-15 02:11:14 +0000507 if revision_ref.startswith('refs/branch-heads'):
508 options.with_branch_heads = True
509
Edward Lesmes07a68342021-04-20 23:39:30 +0000510 mirror = self._GetMirror(url, options, revision, revision_ref)
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000511 if mirror:
512 url = mirror.mirror_path
Edward Lemurdb5c5ad2019-03-07 16:00:24 +0000513
John Budorick882c91e2018-07-12 22:11:41 +0000514 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
515 if remote_ref:
516 # Rewrite remote refs to their local equivalents.
517 revision = ''.join(remote_ref)
518 rev_type = "branch"
519 elif revision.startswith('refs/'):
520 # Local branch? We probably don't want to support, since DEPS should
521 # always specify branches as they are in the upstream repo.
522 rev_type = "branch"
523 else:
524 # hash is also a tag, only make a distinction at checkout
525 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000526
primiano@chromium.org1c127382015-02-17 11:15:40 +0000527 # If we are going to introduce a new project, there is a possibility that
528 # we are syncing back to a state where the project was originally a
529 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
530 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
531 # In such case, we might have a backup of the former .git folder, which can
532 # be used to avoid re-fetching the entire repo again (useful for bisects).
533 backup_dir = self.GetGitBackupDirPath()
534 target_dir = os.path.join(self.checkout_path, '.git')
535 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
536 gclient_utils.safe_makedirs(self.checkout_path)
537 os.rename(backup_dir, target_dir)
538 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800539 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000540
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000541 if (not os.path.exists(self.checkout_path) or
542 (os.path.isdir(self.checkout_path) and
543 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000544 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000545 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000546 try:
John Budorick882c91e2018-07-12 22:11:41 +0000547 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000548 except subprocess2.CalledProcessError:
549 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000550 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000551 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800552 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20 +0000553 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambrec2f74c12019-03-19 05:55:53 +0000554 file_list.extend(
Edward Lemur979fa782019-08-13 22:44:05 +0000555 [os.path.join(self.checkout_path, f) for f in files])
John Budorick21a51b32018-09-19 19:39:20 +0000556 if mirror:
557 self._Capture(
558 ['remote', 'set-url', '--push', 'origin', mirror.url])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000559 if not verbose:
560 # Make the output a little prettier. It's nice to have some whitespace
561 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000562 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000563 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000564
John Budorick21a51b32018-09-19 19:39:20 +0000565 if mirror:
566 self._Capture(
567 ['remote', 'set-url', '--push', 'origin', mirror.url])
568
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000569 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000570 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000571 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
572 return self._Capture(['rev-parse', '--verify', 'HEAD'])
573
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000574 self._maybe_break_locks(options)
575
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000576 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000577 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000578
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000579 # See if the url has changed (the unittests use git://foo for the url, let
580 # that through).
581 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
582 return_early = False
583 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
584 # unit test pass. (and update the comment above)
585 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
586 # This allows devs to use experimental repos which have a different url
587 # but whose branch(s) are the same as official repos.
Raul Tambrecd862e32019-05-10 21:19:00 +0000588 if (current_url.rstrip('/') != url.rstrip('/') and url != 'git://foo' and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000589 subprocess2.capture(
Aaron Gableac9b0f32019-04-18 17:38:37 +0000590 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000591 cwd=self.checkout_path).strip() != 'False'):
Anthony Polito486f1812020-08-04 23:40:33 +0000592 self.Print('_____ switching %s from %s to new upstream %s' % (
593 self.relpath, current_url, url))
iannucci@chromium.org78514212014-08-20 23:08:00 +0000594 if not (options.force or options.reset):
595 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700596 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000597 # Switch over to the new upstream
598 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000599 if mirror:
600 with open(os.path.join(
601 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
602 'w') as fh:
603 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000604 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
605 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000606
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000607 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000608 else:
John Budorick882c91e2018-07-12 22:11:41 +0000609 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000610
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000611 if return_early:
612 return self._Capture(['rev-parse', '--verify', 'HEAD'])
613
msb@chromium.org5bde4852009-12-14 16:47:12 +0000614 cur_branch = self._GetCurrentBranch()
615
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000616 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000617 # 0) HEAD is detached. Probably from our initial clone.
618 # - make sure HEAD is contained by a named ref, then update.
619 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700620 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000621 # - try to rebase onto the new hash or branch
622 # 2) current branch is tracking a remote branch with local committed
623 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000624 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000625 # 3) current branch is tracking a remote branch w/or w/out changes, and
626 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000627 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000628 # 4) current branch is tracking a remote branch, but DEPS switches to a
629 # different remote branch, and
630 # a) current branch has no local changes, and --force:
631 # - checkout new branch
632 # b) current branch has local changes, and --force and --reset:
633 # - checkout new branch
634 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000635
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000636 # GetUpstreamBranch returns something like 'refs/remotes/origin/main' for
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000637 # a tracking branch
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000638 # or 'main' if not a tracking branch (it's based on a specific rev/hash)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000639 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000640 if cur_branch is None:
641 upstream_branch = None
642 current_type = "detached"
643 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000644 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000645 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
646 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
647 current_type = "hash"
648 logging.debug("Current branch is not tracking an upstream (remote)"
649 " branch.")
650 elif upstream_branch.startswith('refs/remotes'):
651 current_type = "branch"
652 else:
653 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000654
Edward Lemur579c9862018-07-13 23:17:51 +0000655 self._SetFetchConfig(options)
Edward Lemur579c9862018-07-13 23:17:51 +0000656
Michael Spang73fac912019-03-08 18:44:19 +0000657 # Fetch upstream if we don't already have |revision|.
John Budorick882c91e2018-07-12 22:11:41 +0000658 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
Michael Spang73fac912019-03-08 18:44:19 +0000659 self._Fetch(options, prune=options.force)
660
661 if not scm.GIT.IsValidRevision(self.checkout_path, revision,
662 sha_only=True):
663 # Update the remotes first so we have all the refs.
664 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
665 cwd=self.checkout_path)
666 if verbose:
667 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000668
John Budorick882c91e2018-07-12 22:11:41 +0000669 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200670
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000671 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000672 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000673 target = 'HEAD'
674 if options.upstream and upstream_branch:
675 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800676 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000677
msb@chromium.org786fb682010-06-02 15:16:23 +0000678 if current_type == 'detached':
679 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800680 # We just did a Scrub, this is as clean as it's going to get. In
681 # particular if HEAD is a commit that contains two versions of the same
682 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
683 # to actually "Clean" the checkout; that commit is uncheckoutable on this
684 # system. The best we can do is carry forward to the checkout step.
685 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000686 self._CheckClean(revision)
687 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000688 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000689 self.Print('Up-to-date; skipping checkout.')
690 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000691 # 'git checkout' may need to overwrite existing untracked files. Allow
692 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000693 self._Checkout(
694 options,
John Budorick882c91e2018-07-12 22:11:41 +0000695 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000696 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000697 quiet=True,
698 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000699 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000700 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000701 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000702 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700703 # Can't find a merge-base since we don't know our upstream. That makes
704 # this command VERY likely to produce a rebase failure. For now we
705 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000706 upstream_branch = self.remote
707 if options.revision or deps_revision:
708 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700709 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700710 printed_path=printed_path, merge=options.merge)
711 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000712 elif rev_type == 'hash':
713 # case 2
714 self._AttemptRebase(upstream_branch, file_list, options,
715 newbase=revision, printed_path=printed_path,
716 merge=options.merge)
717 printed_path = True
718 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000719 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000720 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000721 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000722 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000723 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000724 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000725 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000726 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
727 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000728 force_switch = False
729 if options.force:
730 try:
John Budorick882c91e2018-07-12 22:11:41 +0000731 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000732 # case 4a
733 force_switch = True
734 except gclient_utils.Error as e:
735 if options.reset:
736 # case 4b
737 force_switch = True
738 else:
739 switch_error = '%s\n%s' % (e.message, switch_error)
740 if force_switch:
741 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000742 (upstream_branch, new_base))
743 switch_branch = 'gclient_' + remote_ref[1]
744 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000745 self._Checkout(options, switch_branch, force=True, quiet=True)
746 else:
747 # case 4c
748 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000749 else:
750 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800751 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000752 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000753 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000754 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000755 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000756 if options.merge:
757 merge_args.append('--ff')
758 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000759 merge_args.append('--ff-only')
760 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000761 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000762 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700763 rebase_files = []
Edward Lemur979fa782019-08-13 22:44:05 +0000764 if re.match(b'fatal: Not possible to fast-forward, aborting.',
765 e.stderr):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000766 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000767 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700768 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000769 printed_path = True
770 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000771 if not options.auto_rebase:
772 try:
773 action = self._AskForData(
774 'Cannot %s, attempt to rebase? '
775 '(y)es / (q)uit / (s)kip : ' %
776 ('merge' if options.merge else 'fast-forward merge'),
777 options)
778 except ValueError:
779 raise gclient_utils.Error('Invalid Character')
780 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700781 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000782 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000783 printed_path = True
784 break
Aravind Vasudevan22bf6052022-01-24 21:11:19 +0000785
786 if re.match(r'quit|q', action, re.I):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000787 raise gclient_utils.Error("Can't fast-forward, please merge or "
788 "rebase manually.\n"
789 "cd %s && git " % self.checkout_path
790 + "rebase %s" % upstream_branch)
Aravind Vasudevan22bf6052022-01-24 21:11:19 +0000791
792 if re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000793 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000794 return
Aravind Vasudevan22bf6052022-01-24 21:11:19 +0000795
796 self.Print('Input not recognized')
Edward Lemur979fa782019-08-13 22:44:05 +0000797 elif re.match(b"error: Your local changes to '.*' would be "
798 b"overwritten by merge. Aborting.\nPlease, commit your "
799 b"changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000800 e.stderr):
801 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000802 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700803 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000804 printed_path = True
Edward Lemur979fa782019-08-13 22:44:05 +0000805 raise gclient_utils.Error(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000806 else:
807 # Some other problem happened with the merge
808 logging.error("Error during fast-forward merge in %s!" % self.relpath)
Edward Lemur979fa782019-08-13 22:44:05 +0000809 self.Print(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000810 raise
811 else:
812 # Fast-forward merge was successful
813 if not re.match('Already up-to-date.', merge_output) or verbose:
814 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700815 self.Print('_____ %s at %s' % (self.relpath, revision),
816 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000817 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000818 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000819 if not verbose:
820 # Make the output a little prettier. It's nice to have some
821 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000822 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000823
agablec3937b92016-10-25 10:13:03 -0700824 if file_list is not None:
825 file_list.extend(
826 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000827
828 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000829 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700830 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000831 '\nConflict while rebasing this branch.\n'
832 'Fix the conflict and run gclient again.\n'
833 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700834 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000835
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000836 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000837 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
838 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000839
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000840 # If --reset and --delete_unversioned_trees are specified, remove any
841 # untracked directories.
842 if options.reset and options.delete_unversioned_trees:
843 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
844 # merge-base by default), so doesn't include untracked files. So we use
845 # 'git ls-files --directory --others --exclude-standard' here directly.
846 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800847 ['-c', 'core.quotePath=false', 'ls-files',
848 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000849 self.checkout_path)
850 for path in (p for p in paths.splitlines() if p.endswith('/')):
851 full_path = os.path.join(self.checkout_path, path)
852 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000853 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000854 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000855
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000856 return self._Capture(['rev-parse', '--verify', 'HEAD'])
857
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000858 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000859 """Reverts local modifications.
860
861 All reverted files will be appended to file_list.
862 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000863 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000864 # revert won't work if the directory doesn't exist. It needs to
865 # checkout instead.
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000866 self.Print('_____ %s is missing, syncing instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000867 # Don't reuse the args.
868 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000869
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000870 default_rev = "refs/heads/main"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000871 if options.upstream:
872 if self._GetCurrentBranch():
873 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
874 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000875 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000876 if not deps_revision:
877 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000878 if deps_revision.startswith('refs/heads/'):
879 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700880 try:
881 deps_revision = self.GetUsableRev(deps_revision, options)
882 except NoUsableRevError as e:
883 # If the DEPS entry's url and hash changed, try to update the origin.
884 # See also http://crbug.com/520067.
John Budorickd94f8ea2020-03-27 15:55:24 +0000885 logging.warning(
886 "Couldn't find usable revision, will retrying to update instead: %s",
smutae7ea312016-07-18 11:59:41 -0700887 e.message)
888 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000889
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000890 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800891 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000892
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800893 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000894 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000895
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000896 if file_list is not None:
897 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
898
899 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000900 """Returns revision"""
901 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000902
msb@chromium.orge28e4982009-09-25 20:51:45 +0000903 def runhooks(self, options, args, file_list):
904 self.status(options, args, file_list)
905
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000906 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000907 """Display status information."""
908 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000909 self.Print('________ couldn\'t run status in %s:\n'
910 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000911 else:
Anthony Politobb457342019-11-15 22:26:01 +0000912 merge_base = []
913 if self.url:
914 _, base_rev = gclient_utils.SplitUrlRevision(self.url)
915 if base_rev:
916 merge_base = [base_rev]
Aaron Gablef4068aa2017-12-12 15:14:09 -0800917 self._Run(
918 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
Edward Lemur24146be2019-08-01 21:44:52 +0000919 options, always_show_header=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000920 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800921 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000922 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000923
smutae7ea312016-07-18 11:59:41 -0700924 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700925 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700926 sha1 = None
927 if not os.path.isdir(self.checkout_path):
928 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800929 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700930
931 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
932 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700933 else:
agable41e3a6c2016-10-20 11:36:56 -0700934 # May exist in origin, but we don't have it yet, so fetch and look
935 # again.
936 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700937 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
938 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700939
940 if not sha1:
941 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800942 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700943
944 return sha1
945
primiano@chromium.org1c127382015-02-17 11:15:40 +0000946 def GetGitBackupDirPath(self):
947 """Returns the path where the .git folder for the current project can be
948 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
949 return os.path.join(self._root_dir,
950 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
951
Edward Lesmes07a68342021-04-20 23:39:30 +0000952 def _GetMirror(self, url, options, revision=None, revision_ref=None):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000953 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000954 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000955 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000956 mirror_kwargs = {
957 'print_func': self.filter,
Edward Lesmes07a68342021-04-20 23:39:30 +0000958 'refs': [],
959 'commits': [],
hinoka@google.comb1b54572014-04-16 22:29:23 +0000960 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000961 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
962 mirror_kwargs['refs'].append('refs/branch-heads/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000963 elif revision_ref and revision_ref.startswith('refs/branch-heads/'):
964 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000965 if hasattr(options, 'with_tags') and options.with_tags:
966 mirror_kwargs['refs'].append('refs/tags/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000967 elif revision_ref and revision_ref.startswith('refs/tags/'):
968 mirror_kwargs['refs'].append(revision_ref)
Edward Lesmes07a68342021-04-20 23:39:30 +0000969 if revision and not revision.startswith('refs/'):
970 mirror_kwargs['commits'].append(revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000971 return git_cache.Mirror(url, **mirror_kwargs)
972
John Budorick882c91e2018-07-12 22:11:41 +0000973 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800974 """Update a git mirror by fetching the latest commits from the remote,
975 unless mirror already contains revision whose type is sha1 hash.
976 """
John Budorick882c91e2018-07-12 22:11:41 +0000977 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800978 if options.verbose:
979 self.Print('skipping mirror update, it has rev=%s already' % revision,
980 timestamp=False)
981 return
982
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000983 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000984 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000985 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000986 depth = 10
987 else:
988 depth = 10000
989 else:
990 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000991 mirror.populate(verbose=options.verbose,
992 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000993 depth=depth,
Vadim Shtayura08049e22017-10-11 00:14:52 +0000994 lock_timeout=getattr(options, 'lock_timeout', 0))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000995
John Budorick882c91e2018-07-12 22:11:41 +0000996 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000997 """Clone a git repository from the given URL.
998
msb@chromium.org786fb682010-06-02 15:16:23 +0000999 Once we've cloned the repo, we checkout a working branch if the specified
1000 revision is a branch head. If it is a tag or a specific commit, then we
1001 leave HEAD detached as it makes future updates simpler -- in this case the
1002 user should first create a new branch or switch to an existing branch before
1003 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001004 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001005 # git clone doesn't seem to insert a newline properly before printing
1006 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001007 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +00001008 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +00001009 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001010 if self.cache_dir:
1011 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001012 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001013 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001014 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +00001015 # If the parent directory does not exist, Git clone on Windows will not
1016 # create it, so we need to do it manually.
1017 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001018 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001019
1020 template_dir = None
1021 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +00001022 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001023 # In the case of a subproject, the pinned sha is not necessarily the
1024 # head of the remote branch (so we can't just use --depth=N). Instead,
1025 # we tell git to fetch all the remote objects from SHA..HEAD by means of
1026 # a template git dir which has a 'shallow' file pointing to the sha.
1027 template_dir = tempfile.mkdtemp(
1028 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
1029 dir=parent_dir)
1030 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
1031 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
1032 template_file.write(revision)
1033 clone_cmd.append('--template=' + template_dir)
1034 else:
1035 # Otherwise, we're just interested in the HEAD. Just use --depth.
1036 clone_cmd.append('--depth=1')
1037
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001038 tmp_dir = tempfile.mkdtemp(
1039 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
1040 dir=parent_dir)
1041 try:
1042 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001043 if self.print_outbuf:
1044 print_stdout = True
Edward Lemur24146be2019-08-01 21:44:52 +00001045 filter_fn = None
Edward Lemur231f5ea2018-01-31 19:02:36 +01001046 else:
1047 print_stdout = False
Edward Lemur24146be2019-08-01 21:44:52 +00001048 filter_fn = self.filter
Edward Lemur231f5ea2018-01-31 19:02:36 +01001049 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
Edward Lemur24146be2019-08-01 21:44:52 +00001050 print_stdout=print_stdout, filter_fn=filter_fn)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001051 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +00001052 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
1053 os.path.join(self.checkout_path, '.git'))
Edward Lesmesd4e20f22020-07-15 21:11:08 +00001054 # TODO(https://github.com/git-for-windows/git/issues/2569): Remove once
1055 # fixed.
1056 if sys.platform.startswith('win'):
1057 try:
1058 self._Run(['config', '--unset', 'core.worktree'], options,
1059 cwd=self.checkout_path)
1060 except subprocess2.CalledProcessError:
1061 pass
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001062 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001063 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001064 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001065 finally:
1066 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001067 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001068 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001069 if template_dir:
1070 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +00001071 self._SetFetchConfig(options)
1072 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001073 revision = self._AutoFetchRef(options, revision)
1074 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1075 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +00001076 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +00001077 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001078 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +00001079 ('Checked out %s to a detached HEAD. Before making any commits\n'
1080 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1081 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +00001082 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001083
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001084 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001085 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001086 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001087 raise gclient_utils.Error("Background task requires input. Rerun "
1088 "gclient with --jobs=1 so that\n"
1089 "interaction is possible.")
Edward Lesmesae3586b2020-03-23 21:21:14 +00001090 return gclient_utils.AskForData(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001091
1092
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001093 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001094 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001095 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001096 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001097 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001098 revision = upstream
1099 if newbase:
1100 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001101 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001102 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001103 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001104 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001105 printed_path = True
1106 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001107 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001108
1109 if merge:
1110 merge_output = self._Capture(['merge', revision])
1111 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001112 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001113 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001114
1115 # Build the rebase command here using the args
1116 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1117 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001118 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001119 rebase_cmd.append('--verbose')
1120 if newbase:
1121 rebase_cmd.extend(['--onto', newbase])
1122 rebase_cmd.append(upstream)
1123 if branch:
1124 rebase_cmd.append(branch)
1125
1126 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001127 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
Raul Tambreb946b232019-03-26 14:48:46 +00001128 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001129 if (re.match(br'cannot rebase: you have unstaged changes', e.stderr) or
1130 re.match(br'cannot rebase: your index contains uncommitted changes',
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001131 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001132 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001133 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001134 'Cannot rebase because of unstaged changes.\n'
1135 '\'git reset --hard HEAD\' ?\n'
1136 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001137 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001138 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001139 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001140 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001141 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001142 break
Aravind Vasudevan22bf6052022-01-24 21:11:19 +00001143
1144 if re.match(r'quit|q', rebase_action, re.I):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001145 raise gclient_utils.Error("Please merge or rebase manually\n"
1146 "cd %s && git " % self.checkout_path
1147 + "%s" % ' '.join(rebase_cmd))
Aravind Vasudevan22bf6052022-01-24 21:11:19 +00001148
1149 if re.match(r'show|s', rebase_action, re.I):
Edward Lemur979fa782019-08-13 22:44:05 +00001150 self.Print('%s' % e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001151 continue
Aravind Vasudevan22bf6052022-01-24 21:11:19 +00001152
1153 gclient_utils.Error("Input not recognized")
1154 continue
Edward Lemur979fa782019-08-13 22:44:05 +00001155 elif re.search(br'^CONFLICT', e.stdout, re.M):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001156 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1157 "Fix the conflict and run gclient again.\n"
1158 "See 'man git-rebase' for details.\n")
1159 else:
Edward Lemur979fa782019-08-13 22:44:05 +00001160 self.Print(e.stdout.decode('utf-8').strip())
1161 self.Print('Rebase produced error output:\n%s' %
1162 e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001163 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1164 "manually.\ncd %s && git " %
1165 self.checkout_path
1166 + "%s" % ' '.join(rebase_cmd))
1167
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001168 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001169 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001170 # Make the output a little prettier. It's nice to have some
1171 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001172 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001173
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001174 @staticmethod
1175 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001176 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1177 if not ok:
1178 raise gclient_utils.Error('git version %s < minimum required %s' %
1179 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001180
John Budorick882c91e2018-07-12 22:11:41 +00001181 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001182 # Special case handling if all 3 conditions are met:
1183 # * the mirros have recently changed, but deps destination remains same,
1184 # * the git histories of mirrors are conflicting.
1185 # * git cache is used
1186 # This manifests itself in current checkout having invalid HEAD commit on
1187 # most git operations. Since git cache is used, just deleted the .git
1188 # folder, and re-create it by cloning.
1189 try:
1190 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1191 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001192 if (b'fatal: bad object HEAD' in e.stderr
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001193 and self.cache_dir and self.cache_dir in url):
1194 self.Print((
1195 'Likely due to DEPS change with git cache_dir, '
1196 'the current commit points to no longer existing object.\n'
1197 '%s' % e)
1198 )
1199 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001200 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001201 else:
1202 raise
1203
msb@chromium.org786fb682010-06-02 15:16:23 +00001204 def _IsRebasing(self):
1205 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1206 # have a plumbing command to determine whether a rebase is in progress, so
1207 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1208 g = os.path.join(self.checkout_path, '.git')
1209 return (
1210 os.path.isdir(os.path.join(g, "rebase-merge")) or
1211 os.path.isdir(os.path.join(g, "rebase-apply")))
1212
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001213 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001214 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1215 if os.path.exists(lockfile):
1216 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001217 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001218 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1219 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001220 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001221
msb@chromium.org786fb682010-06-02 15:16:23 +00001222 # Make sure the tree is clean; see git-rebase.sh for reference
1223 try:
1224 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001225 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001226 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001227 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001228 '\tYou have unstaged changes.\n'
1229 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001230 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001231 try:
1232 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001233 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001234 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001235 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001236 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001237 '\tYour index contains uncommitted changes\n'
1238 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001239 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001240
agable83faed02016-10-24 14:37:10 -07001241 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001242 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1243 # reference by a commit). If not, error out -- most likely a rebase is
1244 # in progress, try to detect so we can give a better error.
1245 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001246 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1247 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001248 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001249 # Commit is not contained by any rev. See if the user is rebasing:
1250 if self._IsRebasing():
1251 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001252 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001253 '\tAlready in a conflict, i.e. (no branch).\n'
1254 '\tFix the conflict and run gclient again.\n'
1255 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1256 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001257 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001258 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001259 name = ('saved-by-gclient-' +
1260 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001261 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001262 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001263 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001264
msb@chromium.org5bde4852009-12-14 16:47:12 +00001265 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001266 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001267 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001268 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001269 return None
1270 return branch
1271
borenet@google.comc3e09d22014-04-10 13:58:18 +00001272 def _Capture(self, args, **kwargs):
Mike Frysinger286fb162019-09-30 03:14:10 +00001273 set_git_dir = 'cwd' not in kwargs
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001274 kwargs.setdefault('cwd', self.checkout_path)
1275 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001276 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001277 env = scm.GIT.ApplyEnvVars(kwargs)
Mike Frysinger286fb162019-09-30 03:14:10 +00001278 # If an explicit cwd isn't set, then default to the .git/ subdir so we get
1279 # stricter behavior. This can be useful in cases of slight corruption --
1280 # we don't accidentally go corrupting parent git checks too. See
1281 # https://crbug.com/1000825 for an example.
1282 if set_git_dir:
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001283 git_dir = os.path.abspath(os.path.join(self.checkout_path, '.git'))
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001284 # Depending on how the .gclient file was defined, self.checkout_path
1285 # might be set to a unicode string, not a regular string; on Windows
1286 # Python2, we can't set env vars to be unicode strings, so we
1287 # forcibly cast the value to a string before setting it.
1288 env.setdefault('GIT_DIR', str(git_dir))
Raul Tambrecd862e32019-05-10 21:19:00 +00001289 ret = subprocess2.check_output(
1290 ['git'] + args, env=env, **kwargs).decode('utf-8')
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001291 if strip:
1292 ret = ret.strip()
Erik Chene16ffff2019-10-14 20:35:53 +00001293 self.Print('Finished running: %s %s' % ('git', ' '.join(args)))
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001294 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001295
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001296 def _Checkout(self, options, ref, force=False, quiet=None):
1297 """Performs a 'git-checkout' operation.
1298
1299 Args:
1300 options: The configured option set
1301 ref: (str) The branch/commit to checkout
Quinten Yearsley925cedb2020-04-13 17:49:39 +00001302 quiet: (bool/None) Whether or not the checkout should pass '--quiet'; if
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001303 'None', the behavior is inferred from 'options.verbose'.
1304 Returns: (str) The output of the checkout operation
1305 """
1306 if quiet is None:
1307 quiet = (not options.verbose)
1308 checkout_args = ['checkout']
1309 if force:
1310 checkout_args.append('--force')
1311 if quiet:
1312 checkout_args.append('--quiet')
1313 checkout_args.append(ref)
1314 return self._Capture(checkout_args)
1315
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001316 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1317 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001318 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemur9a5e3bd2019-04-02 23:37:45 +00001319 # When updating, the ref is modified to be a remote ref .
1320 # (e.g. refs/heads/NAME becomes refs/remotes/REMOTE/NAME).
1321 # Try to reverse that mapping.
1322 original_ref = scm.GIT.RemoteRefToRef(refspec, self.remote)
1323 if original_ref:
1324 refspec = original_ref + ':' + refspec
1325 # When a mirror is configured, it only fetches
1326 # refs/{heads,branch-heads,tags}/*.
1327 # If asked to fetch other refs, we must fetch those directly from the
1328 # repository, and not from the mirror.
1329 if not original_ref.startswith(
1330 ('refs/heads/', 'refs/branch-heads/', 'refs/tags/')):
1331 remote, _ = gclient_utils.SplitUrlRevision(self.url)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001332 fetch_cmd = cfg + [
1333 'fetch',
1334 remote or self.remote,
1335 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001336 if refspec:
1337 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001338
1339 if prune:
1340 fetch_cmd.append('--prune')
1341 if options.verbose:
1342 fetch_cmd.append('--verbose')
danakjd5c0b562019-11-08 17:27:47 +00001343 if not hasattr(options, 'with_tags') or not options.with_tags:
1344 fetch_cmd.append('--no-tags')
dnj@chromium.org680f2172014-06-25 00:39:32 +00001345 elif quiet:
1346 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001347 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001348
Edward Lemur579c9862018-07-13 23:17:51 +00001349 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001350 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1351 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001352 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001353 try:
1354 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1355 options)
1356 self._Run(['config', 'remote.%s.fetch' % self.remote,
1357 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1358 except subprocess2.CalledProcessError as e:
1359 # If exit code was 5, it means we attempted to unset a config that
1360 # didn't exist. Ignore it.
1361 if e.returncode != 5:
1362 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001363 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001364 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001365 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1366 '^\\+refs/branch-heads/\\*:.*$']
1367 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001368 if hasattr(options, 'with_tags') and options.with_tags:
1369 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1370 '+refs/tags/*:refs/tags/*',
1371 '^\\+refs/tags/\\*:.*$']
1372 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001373
John Budorick882c91e2018-07-12 22:11:41 +00001374 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001375 """Attempts to fetch |revision| if not available in local repo.
1376
1377 Returns possibly updated revision."""
Edward Lemure0ba7b82020-03-11 20:31:32 +00001378 if not scm.GIT.IsValidRevision(self.checkout_path, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001379 self._Fetch(options, refspec=revision)
1380 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1381 return revision
1382
Edward Lemur24146be2019-08-01 21:44:52 +00001383 def _Run(self, args, options, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001384 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001385 kwargs.setdefault('cwd', self.checkout_path)
Edward Lemur24146be2019-08-01 21:44:52 +00001386 kwargs.setdefault('filter_fn', self.filter)
1387 kwargs.setdefault('show_header', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001388 env = scm.GIT.ApplyEnvVars(kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +00001389
agable@chromium.org772efaf2014-04-01 02:35:44 +00001390 cmd = ['git'] + args
Edward Lemur24146be2019-08-01 21:44:52 +00001391 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001392
1393
1394class CipdPackage(object):
1395 """A representation of a single CIPD package."""
1396
John Budorickd3ba72b2018-03-20 12:27:42 -07001397 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001398 self._authority_for_subdir = authority_for_subdir
1399 self._name = name
1400 self._version = version
1401
1402 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001403 def authority_for_subdir(self):
1404 """Whether this package has authority to act on behalf of its subdir.
1405
1406 Some operations should only be performed once per subdirectory. A package
1407 that has authority for its subdirectory is the only package that should
1408 perform such operations.
1409
1410 Returns:
1411 bool; whether this package has subdir authority.
1412 """
1413 return self._authority_for_subdir
1414
1415 @property
1416 def name(self):
1417 return self._name
1418
1419 @property
1420 def version(self):
1421 return self._version
1422
1423
1424class CipdRoot(object):
1425 """A representation of a single CIPD root."""
1426 def __init__(self, root_dir, service_url):
1427 self._all_packages = set()
1428 self._mutator_lock = threading.Lock()
1429 self._packages_by_subdir = collections.defaultdict(list)
1430 self._root_dir = root_dir
1431 self._service_url = service_url
1432
1433 def add_package(self, subdir, package, version):
1434 """Adds a package to this CIPD root.
1435
1436 As far as clients are concerned, this grants both root and subdir authority
1437 to packages arbitrarily. (The implementation grants root authority to the
1438 first package added and subdir authority to the first package added for that
1439 subdir, but clients should not depend on or expect that behavior.)
1440
1441 Args:
1442 subdir: str; relative path to where the package should be installed from
1443 the cipd root directory.
1444 package: str; the cipd package name.
1445 version: str; the cipd package version.
1446 Returns:
1447 CipdPackage; the package that was created and added to this root.
1448 """
1449 with self._mutator_lock:
1450 cipd_package = CipdPackage(
1451 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001452 not self._packages_by_subdir[subdir])
1453 self._all_packages.add(cipd_package)
1454 self._packages_by_subdir[subdir].append(cipd_package)
1455 return cipd_package
1456
1457 def packages(self, subdir):
1458 """Get the list of configured packages for the given subdir."""
1459 return list(self._packages_by_subdir[subdir])
1460
1461 def clobber(self):
1462 """Remove the .cipd directory.
1463
1464 This is useful for forcing ensure to redownload and reinitialize all
1465 packages.
1466 """
1467 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001468 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001469 try:
1470 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1471 except OSError:
1472 if os.path.exists(cipd_cache_dir):
1473 raise
1474
1475 @contextlib.contextmanager
1476 def _create_ensure_file(self):
1477 try:
Edward Lesmes05934952019-12-19 20:38:09 +00001478 contents = '$ParanoidMode CheckPresence\n\n'
1479 for subdir, packages in sorted(self._packages_by_subdir.items()):
1480 contents += '@Subdir %s\n' % subdir
1481 for package in sorted(packages, key=lambda p: p.name):
1482 contents += '%s %s\n' % (package.name, package.version)
1483 contents += '\n'
John Budorick0f7b2002018-01-19 15:46:17 -08001484 ensure_file = None
1485 with tempfile.NamedTemporaryFile(
Edward Lesmes05934952019-12-19 20:38:09 +00001486 suffix='.ensure', delete=False, mode='wb') as ensure_file:
1487 ensure_file.write(contents.encode('utf-8', 'replace'))
John Budorick0f7b2002018-01-19 15:46:17 -08001488 yield ensure_file.name
1489 finally:
1490 if ensure_file is not None and os.path.exists(ensure_file.name):
1491 os.remove(ensure_file.name)
1492
1493 def ensure(self):
1494 """Run `cipd ensure`."""
1495 with self._mutator_lock:
1496 with self._create_ensure_file() as ensure_file:
1497 cmd = [
1498 'cipd', 'ensure',
1499 '-log-level', 'error',
1500 '-root', self.root_dir,
1501 '-ensure-file', ensure_file,
1502 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001503 gclient_utils.CheckCallAndFilter(
1504 cmd, print_stdout=True, show_header=True)
John Budorick0f7b2002018-01-19 15:46:17 -08001505
John Budorickd3ba72b2018-03-20 12:27:42 -07001506 def run(self, command):
1507 if command == 'update':
1508 self.ensure()
1509 elif command == 'revert':
1510 self.clobber()
1511 self.ensure()
1512
John Budorick0f7b2002018-01-19 15:46:17 -08001513 def created_package(self, package):
1514 """Checks whether this root created the given package.
1515
1516 Args:
1517 package: CipdPackage; the package to check.
1518 Returns:
1519 bool; whether this root created the given package.
1520 """
1521 return package in self._all_packages
1522
1523 @property
1524 def root_dir(self):
1525 return self._root_dir
1526
1527 @property
1528 def service_url(self):
1529 return self._service_url
1530
1531
1532class CipdWrapper(SCMWrapper):
1533 """Wrapper for CIPD.
1534
1535 Currently only supports chrome-infra-packages.appspot.com.
1536 """
John Budorick3929e9e2018-02-04 18:18:07 -08001537 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001538
1539 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1540 out_cb=None, root=None, package=None):
1541 super(CipdWrapper, self).__init__(
1542 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1543 out_cb=out_cb)
1544 assert root.created_package(package)
1545 self._package = package
1546 self._root = root
1547
1548 #override
1549 def GetCacheMirror(self):
1550 return None
1551
1552 #override
1553 def GetActualRemoteURL(self, options):
1554 return self._root.service_url
1555
1556 #override
1557 def DoesRemoteURLMatch(self, options):
1558 del options
1559 return True
1560
1561 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001562 """Does nothing.
1563
1564 CIPD packages should be reverted at the root by running
1565 `CipdRoot.run('revert')`.
1566 """
John Budorick0f7b2002018-01-19 15:46:17 -08001567
1568 def diff(self, options, args, file_list):
1569 """CIPD has no notion of diffing."""
John Budorick0f7b2002018-01-19 15:46:17 -08001570
1571 def pack(self, options, args, file_list):
1572 """CIPD has no notion of diffing."""
John Budorick0f7b2002018-01-19 15:46:17 -08001573
1574 def revinfo(self, options, args, file_list):
1575 """Grab the instance ID."""
1576 try:
1577 tmpdir = tempfile.mkdtemp()
1578 describe_json_path = os.path.join(tmpdir, 'describe.json')
1579 cmd = [
1580 'cipd', 'describe',
1581 self._package.name,
1582 '-log-level', 'error',
1583 '-version', self._package.version,
1584 '-json-output', describe_json_path
1585 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001586 gclient_utils.CheckCallAndFilter(cmd)
John Budorick0f7b2002018-01-19 15:46:17 -08001587 with open(describe_json_path) as f:
1588 describe_json = json.load(f)
1589 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1590 finally:
1591 gclient_utils.rmtree(tmpdir)
1592
1593 def status(self, options, args, file_list):
1594 pass
1595
1596 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001597 """Does nothing.
1598
1599 CIPD packages should be updated at the root by running
1600 `CipdRoot.run('update')`.
1601 """