blob: d79f07e9262d6d9385d038229025a0bf66dbe63a [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('/'))
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +0000168 else:
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')
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +0000384 elif target_rev.startswith(('refs/heads/', 'refs/branch-heads')):
Edward Lesmesf627d9f2020-07-23 19:50:50 +0000385 # If |target_rev| is in refs/heads/** or refs/branch-heads/**, try first
386 # to find the corresponding remote ref for it, since |target_rev| might
387 # point to a local ref which is not up to date with the corresponding
388 # remote ref.
Edward Lemur3acbc742019-05-30 17:57:35 +0000389 remote_ref = ''.join(scm.GIT.RefToRemoteRef(target_rev, self.remote))
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000390 self.Print('Trying the corresponding remote ref for %r: %r\n' % (
Edward Lemur3acbc742019-05-30 17:57:35 +0000391 target_rev, remote_ref))
392 if scm.GIT.IsValidRevision(self.checkout_path, remote_ref):
Josip Sokcevic3d7cbce2021-10-05 20:48:04 +0000393 # refs/remotes may need to be updated to cleanly cherry-pick changes.
394 # See https://crbug.com/1255178.
395 self._Capture(['fetch', '--no-tags', self.remote, target_rev])
Edward Lemur3acbc742019-05-30 17:57:35 +0000396 target_rev = remote_ref
397 elif not scm.GIT.IsValidRevision(self.checkout_path, target_rev):
398 # Fetch |target_rev| if it's not already available.
399 url, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmes07a68342021-04-20 23:39:30 +0000400 mirror = self._GetMirror(url, options, target_rev, target_rev)
Edward Lemur3acbc742019-05-30 17:57:35 +0000401 if mirror:
402 rev_type = 'branch' if target_rev.startswith('refs/') else 'hash'
403 self._UpdateMirrorIfNotContains(mirror, options, rev_type, target_rev)
404 self._Fetch(options, refspec=target_rev)
Edward Lemura0ffbe42019-05-01 16:52:18 +0000405
Edward Lemur3acbc742019-05-30 17:57:35 +0000406 self.Print('===Applying patch===')
407 self.Print('Revision to patch is %r @ %r.' % (patch_repo, patch_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000408 self.Print('Current dir is %r' % self.checkout_path)
Edward Lesmesc621b212018-03-21 20:26:56 -0400409 self._Capture(['reset', '--hard'])
danakjd5c0b562019-11-08 17:27:47 +0000410 self._Capture(['fetch', '--no-tags', patch_repo, patch_rev])
Edward Lemurca7d8812018-07-24 17:42:45 +0000411 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400412
Edward Lemur3acbc742019-05-30 17:57:35 +0000413 if not options.rebase_patch_ref:
414 self._Capture(['checkout', patch_rev])
Robert Iannuccic39a7782019-11-01 18:30:33 +0000415 # Adjust base_rev to be the first parent of our checked out patch ref;
416 # This will allow us to correctly extend `file_list`, and will show the
417 # correct file-list to programs which do `git diff --cached` expecting to
418 # see the patch diff.
419 base_rev = self._Capture(['rev-parse', patch_rev+'~'])
420
Edward Lemur3acbc742019-05-30 17:57:35 +0000421 else:
Robert Iannuccic39a7782019-11-01 18:30:33 +0000422 self.Print('Will cherrypick %r .. %r on top of %r.' % (
423 target_rev, patch_rev, base_rev))
Edward Lemur3acbc742019-05-30 17:57:35 +0000424 try:
425 if scm.GIT.IsAncestor(self.checkout_path, patch_rev, target_rev):
426 # If |patch_rev| is an ancestor of |target_rev|, check it out.
Edward Lemurca7d8812018-07-24 17:42:45 +0000427 self._Capture(['checkout', patch_rev])
428 else:
429 # If a change was uploaded on top of another change, which has already
430 # landed, one of the commits in the cherry-pick range will be
431 # redundant, since it has already landed and its changes incorporated
432 # in the tree.
433 # We pass '--keep-redundant-commits' to ignore those changes.
Edward Lemur3acbc742019-05-30 17:57:35 +0000434 self._Capture(['cherry-pick', target_rev + '..' + patch_rev,
Josip Sokcevic804165b2021-11-30 01:16:39 +0000435 '--keep-redundant-commits'])
Edward Lemurca7d8812018-07-24 17:42:45 +0000436
Edward Lemur3acbc742019-05-30 17:57:35 +0000437 except subprocess2.CalledProcessError as e:
438 self.Print('Failed to apply patch.')
439 self.Print('Revision to patch was %r @ %r.' % (patch_repo, patch_rev))
440 self.Print('Tried to cherrypick %r .. %r on top of %r.' % (
441 target_rev, patch_rev, base_rev))
442 self.Print('Current dir is %r' % self.checkout_path)
443 self.Print('git returned non-zero exit status %s:\n%s' % (
Edward Lemur979fa782019-08-13 22:44:05 +0000444 e.returncode, e.stderr.decode('utf-8')))
Edward Lemur3acbc742019-05-30 17:57:35 +0000445 # Print the current status so that developers know what changes caused
446 # the patch failure, since git cherry-pick doesn't show that
447 # information.
448 self.Print(self._Capture(['status']))
449 try:
450 self._Capture(['cherry-pick', '--abort'])
451 except subprocess2.CalledProcessError:
452 pass
453 raise
Edward Lemurca7d8812018-07-24 17:42:45 +0000454
Edward Lemur3acbc742019-05-30 17:57:35 +0000455 if file_list is not None:
456 file_list.extend(self._GetDiffFilenames(base_rev))
Edward Lemurca7d8812018-07-24 17:42:45 +0000457
Edward Lesmesc621b212018-03-21 20:26:56 -0400458 if options.reset_patch_ref:
459 self._Capture(['reset', '--soft', base_rev])
460
msb@chromium.orge28e4982009-09-25 20:51:45 +0000461 def update(self, options, args, file_list):
462 """Runs git to update or transparently checkout the working copy.
463
464 All updated files will be appended to file_list.
465
466 Raises:
467 Error: if can't get URL for relative path.
468 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000469 if args:
470 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
471
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000472 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000473
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000474 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000475 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000476 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000477 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000478 # Override the revision number.
479 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000480 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000481 # Check again for a revision in case an initial ref was specified
482 # in the url, for example bla.git@refs/heads/custombranch
483 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000484 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000485 if not revision:
Josip Sokcevic091f5ac2021-01-14 23:14:21 +0000486 # If a dependency is not pinned, track the default remote branch.
487 revision = scm.GIT.GetRemoteHeadRef(self.checkout_path, self.url,
488 self.remote)
Edward Lesmes4ea67bb2021-04-20 17:33:52 +0000489 if revision.startswith('origin/'):
490 revision = 'refs/remotes/' + revision
msb@chromium.orge28e4982009-09-25 20:51:45 +0000491
Tomasz Wiszkowskid4e66882021-08-19 21:35:09 +0000492 if managed and platform.system() == 'Windows':
szager@chromium.org8a139702014-06-20 15:55:01 +0000493 self._DisableHooks()
494
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000495 printed_path = False
496 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000497 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700498 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000499 verbose = ['--verbose']
500 printed_path = True
501
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000502 revision_ref = revision
503 if ':' in revision:
504 revision_ref, _, revision = revision.partition(':')
505
Edward Lesmes8073a502020-04-15 02:11:14 +0000506 if revision_ref.startswith('refs/branch-heads'):
507 options.with_branch_heads = True
508
Edward Lesmes07a68342021-04-20 23:39:30 +0000509 mirror = self._GetMirror(url, options, revision, revision_ref)
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000510 if mirror:
511 url = mirror.mirror_path
Edward Lemurdb5c5ad2019-03-07 16:00:24 +0000512
John Budorick882c91e2018-07-12 22:11:41 +0000513 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
514 if remote_ref:
515 # Rewrite remote refs to their local equivalents.
516 revision = ''.join(remote_ref)
517 rev_type = "branch"
518 elif revision.startswith('refs/'):
519 # Local branch? We probably don't want to support, since DEPS should
520 # always specify branches as they are in the upstream repo.
521 rev_type = "branch"
522 else:
523 # hash is also a tag, only make a distinction at checkout
524 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000525
primiano@chromium.org1c127382015-02-17 11:15:40 +0000526 # If we are going to introduce a new project, there is a possibility that
527 # we are syncing back to a state where the project was originally a
528 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
529 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
530 # In such case, we might have a backup of the former .git folder, which can
531 # be used to avoid re-fetching the entire repo again (useful for bisects).
532 backup_dir = self.GetGitBackupDirPath()
533 target_dir = os.path.join(self.checkout_path, '.git')
534 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
535 gclient_utils.safe_makedirs(self.checkout_path)
536 os.rename(backup_dir, target_dir)
537 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800538 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000539
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000540 if (not os.path.exists(self.checkout_path) or
541 (os.path.isdir(self.checkout_path) and
542 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000543 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000544 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000545 try:
John Budorick882c91e2018-07-12 22:11:41 +0000546 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000547 except subprocess2.CalledProcessError:
548 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000549 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000550 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800551 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20 +0000552 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambrec2f74c12019-03-19 05:55:53 +0000553 file_list.extend(
Edward Lemur979fa782019-08-13 22:44:05 +0000554 [os.path.join(self.checkout_path, f) for f in files])
John Budorick21a51b32018-09-19 19:39:20 +0000555 if mirror:
556 self._Capture(
557 ['remote', 'set-url', '--push', 'origin', mirror.url])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000558 if not verbose:
559 # Make the output a little prettier. It's nice to have some whitespace
560 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000561 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000562 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000563
John Budorick21a51b32018-09-19 19:39:20 +0000564 if mirror:
565 self._Capture(
566 ['remote', 'set-url', '--push', 'origin', mirror.url])
567
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000568 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000569 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000570 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
571 return self._Capture(['rev-parse', '--verify', 'HEAD'])
572
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000573 self._maybe_break_locks(options)
574
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000575 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000576 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000577
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000578 # See if the url has changed (the unittests use git://foo for the url, let
579 # that through).
580 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
581 return_early = False
582 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
583 # unit test pass. (and update the comment above)
584 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
585 # This allows devs to use experimental repos which have a different url
586 # but whose branch(s) are the same as official repos.
Raul Tambrecd862e32019-05-10 21:19:00 +0000587 if (current_url.rstrip('/') != url.rstrip('/') and url != 'git://foo' and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000588 subprocess2.capture(
Aaron Gableac9b0f32019-04-18 17:38:37 +0000589 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000590 cwd=self.checkout_path).strip() != 'False'):
Anthony Polito486f1812020-08-04 23:40:33 +0000591 self.Print('_____ switching %s from %s to new upstream %s' % (
592 self.relpath, current_url, url))
iannucci@chromium.org78514212014-08-20 23:08:00 +0000593 if not (options.force or options.reset):
594 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700595 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000596 # Switch over to the new upstream
597 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000598 if mirror:
599 with open(os.path.join(
600 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
601 'w') as fh:
602 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000603 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
604 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000605
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000606 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000607 else:
John Budorick882c91e2018-07-12 22:11:41 +0000608 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000609
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000610 if return_early:
611 return self._Capture(['rev-parse', '--verify', 'HEAD'])
612
msb@chromium.org5bde4852009-12-14 16:47:12 +0000613 cur_branch = self._GetCurrentBranch()
614
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000615 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000616 # 0) HEAD is detached. Probably from our initial clone.
617 # - make sure HEAD is contained by a named ref, then update.
618 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700619 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000620 # - try to rebase onto the new hash or branch
621 # 2) current branch is tracking a remote branch with local committed
622 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000623 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000624 # 3) current branch is tracking a remote branch w/or w/out changes, and
625 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000626 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000627 # 4) current branch is tracking a remote branch, but DEPS switches to a
628 # different remote branch, and
629 # a) current branch has no local changes, and --force:
630 # - checkout new branch
631 # b) current branch has local changes, and --force and --reset:
632 # - checkout new branch
633 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000634
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000635 # GetUpstreamBranch returns something like 'refs/remotes/origin/main' for
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000636 # a tracking branch
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000637 # or 'main' if not a tracking branch (it's based on a specific rev/hash)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000638 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000639 if cur_branch is None:
640 upstream_branch = None
641 current_type = "detached"
642 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000643 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000644 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
645 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
646 current_type = "hash"
647 logging.debug("Current branch is not tracking an upstream (remote)"
648 " branch.")
649 elif upstream_branch.startswith('refs/remotes'):
650 current_type = "branch"
651 else:
652 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000653
Edward Lemur579c9862018-07-13 23:17:51 +0000654 self._SetFetchConfig(options)
Edward Lemur579c9862018-07-13 23:17:51 +0000655
Michael Spang73fac912019-03-08 18:44:19 +0000656 # Fetch upstream if we don't already have |revision|.
John Budorick882c91e2018-07-12 22:11:41 +0000657 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
Michael Spang73fac912019-03-08 18:44:19 +0000658 self._Fetch(options, prune=options.force)
659
660 if not scm.GIT.IsValidRevision(self.checkout_path, revision,
661 sha_only=True):
662 # Update the remotes first so we have all the refs.
663 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
664 cwd=self.checkout_path)
665 if verbose:
666 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000667
John Budorick882c91e2018-07-12 22:11:41 +0000668 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200669
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000670 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000671 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000672 target = 'HEAD'
673 if options.upstream and upstream_branch:
674 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800675 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000676
msb@chromium.org786fb682010-06-02 15:16:23 +0000677 if current_type == 'detached':
678 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800679 # We just did a Scrub, this is as clean as it's going to get. In
680 # particular if HEAD is a commit that contains two versions of the same
681 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
682 # to actually "Clean" the checkout; that commit is uncheckoutable on this
683 # system. The best we can do is carry forward to the checkout step.
684 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000685 self._CheckClean(revision)
686 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000687 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000688 self.Print('Up-to-date; skipping checkout.')
689 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000690 # 'git checkout' may need to overwrite existing untracked files. Allow
691 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000692 self._Checkout(
693 options,
John Budorick882c91e2018-07-12 22:11:41 +0000694 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000695 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000696 quiet=True,
697 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000698 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000699 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000700 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000701 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700702 # Can't find a merge-base since we don't know our upstream. That makes
703 # this command VERY likely to produce a rebase failure. For now we
704 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000705 upstream_branch = self.remote
706 if options.revision or deps_revision:
707 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700708 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700709 printed_path=printed_path, merge=options.merge)
710 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000711 elif rev_type == 'hash':
712 # case 2
713 self._AttemptRebase(upstream_branch, file_list, options,
714 newbase=revision, printed_path=printed_path,
715 merge=options.merge)
716 printed_path = True
717 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000718 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000719 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000720 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000721 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000722 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000723 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000724 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000725 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
726 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000727 force_switch = False
728 if options.force:
729 try:
John Budorick882c91e2018-07-12 22:11:41 +0000730 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000731 # case 4a
732 force_switch = True
733 except gclient_utils.Error as e:
734 if options.reset:
735 # case 4b
736 force_switch = True
737 else:
738 switch_error = '%s\n%s' % (e.message, switch_error)
739 if force_switch:
740 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000741 (upstream_branch, new_base))
742 switch_branch = 'gclient_' + remote_ref[1]
743 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000744 self._Checkout(options, switch_branch, force=True, quiet=True)
745 else:
746 # case 4c
747 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000748 else:
749 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800750 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000751 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000752 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000753 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000754 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000755 if options.merge:
756 merge_args.append('--ff')
757 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000758 merge_args.append('--ff-only')
759 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000760 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000761 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700762 rebase_files = []
Edward Lemur979fa782019-08-13 22:44:05 +0000763 if re.match(b'fatal: Not possible to fast-forward, aborting.',
764 e.stderr):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000765 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000766 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700767 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000768 printed_path = True
769 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000770 if not options.auto_rebase:
771 try:
772 action = self._AskForData(
773 'Cannot %s, attempt to rebase? '
774 '(y)es / (q)uit / (s)kip : ' %
775 ('merge' if options.merge else 'fast-forward merge'),
776 options)
777 except ValueError:
778 raise gclient_utils.Error('Invalid Character')
779 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700780 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000781 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000782 printed_path = True
783 break
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +0000784 elif re.match(r'quit|q', action, re.I):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000785 raise gclient_utils.Error("Can't fast-forward, please merge or "
786 "rebase manually.\n"
787 "cd %s && git " % self.checkout_path
788 + "rebase %s" % upstream_branch)
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +0000789 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000790 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000791 return
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +0000792 else:
793 self.Print('Input not recognized')
Edward Lemur979fa782019-08-13 22:44:05 +0000794 elif re.match(b"error: Your local changes to '.*' would be "
795 b"overwritten by merge. Aborting.\nPlease, commit your "
796 b"changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000797 e.stderr):
798 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000799 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700800 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000801 printed_path = True
Edward Lemur979fa782019-08-13 22:44:05 +0000802 raise gclient_utils.Error(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000803 else:
804 # Some other problem happened with the merge
805 logging.error("Error during fast-forward merge in %s!" % self.relpath)
Edward Lemur979fa782019-08-13 22:44:05 +0000806 self.Print(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000807 raise
808 else:
809 # Fast-forward merge was successful
810 if not re.match('Already up-to-date.', merge_output) or verbose:
811 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700812 self.Print('_____ %s at %s' % (self.relpath, revision),
813 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000814 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000815 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000816 if not verbose:
817 # Make the output a little prettier. It's nice to have some
818 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000819 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000820
agablec3937b92016-10-25 10:13:03 -0700821 if file_list is not None:
822 file_list.extend(
823 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000824
825 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000826 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700827 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000828 '\nConflict while rebasing this branch.\n'
829 'Fix the conflict and run gclient again.\n'
830 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700831 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000832
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000833 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000834 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
835 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000836
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000837 # If --reset and --delete_unversioned_trees are specified, remove any
838 # untracked directories.
839 if options.reset and options.delete_unversioned_trees:
840 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
841 # merge-base by default), so doesn't include untracked files. So we use
842 # 'git ls-files --directory --others --exclude-standard' here directly.
843 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800844 ['-c', 'core.quotePath=false', 'ls-files',
845 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000846 self.checkout_path)
847 for path in (p for p in paths.splitlines() if p.endswith('/')):
848 full_path = os.path.join(self.checkout_path, path)
849 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000850 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000851 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000852
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000853 return self._Capture(['rev-parse', '--verify', 'HEAD'])
854
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000855 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000856 """Reverts local modifications.
857
858 All reverted files will be appended to file_list.
859 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000860 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000861 # revert won't work if the directory doesn't exist. It needs to
862 # checkout instead.
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000863 self.Print('_____ %s is missing, syncing instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000864 # Don't reuse the args.
865 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000866
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000867 default_rev = "refs/heads/main"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000868 if options.upstream:
869 if self._GetCurrentBranch():
870 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
871 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000872 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000873 if not deps_revision:
874 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000875 if deps_revision.startswith('refs/heads/'):
876 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700877 try:
878 deps_revision = self.GetUsableRev(deps_revision, options)
879 except NoUsableRevError as e:
880 # If the DEPS entry's url and hash changed, try to update the origin.
881 # See also http://crbug.com/520067.
John Budorickd94f8ea2020-03-27 15:55:24 +0000882 logging.warning(
883 "Couldn't find usable revision, will retrying to update instead: %s",
smutae7ea312016-07-18 11:59:41 -0700884 e.message)
885 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000886
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000887 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800888 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000889
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800890 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000891 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000892
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000893 if file_list is not None:
894 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
895
896 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000897 """Returns revision"""
898 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000899
msb@chromium.orge28e4982009-09-25 20:51:45 +0000900 def runhooks(self, options, args, file_list):
901 self.status(options, args, file_list)
902
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000903 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000904 """Display status information."""
905 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000906 self.Print('________ couldn\'t run status in %s:\n'
907 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000908 else:
Anthony Politobb457342019-11-15 22:26:01 +0000909 merge_base = []
910 if self.url:
911 _, base_rev = gclient_utils.SplitUrlRevision(self.url)
912 if base_rev:
913 merge_base = [base_rev]
Aaron Gablef4068aa2017-12-12 15:14:09 -0800914 self._Run(
915 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
Edward Lemur24146be2019-08-01 21:44:52 +0000916 options, always_show_header=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000917 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800918 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000919 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000920
smutae7ea312016-07-18 11:59:41 -0700921 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700922 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700923 sha1 = None
924 if not os.path.isdir(self.checkout_path):
925 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800926 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700927
928 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
929 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700930 else:
agable41e3a6c2016-10-20 11:36:56 -0700931 # May exist in origin, but we don't have it yet, so fetch and look
932 # again.
933 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700934 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
935 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700936
937 if not sha1:
938 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800939 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700940
941 return sha1
942
primiano@chromium.org1c127382015-02-17 11:15:40 +0000943 def GetGitBackupDirPath(self):
944 """Returns the path where the .git folder for the current project can be
945 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
946 return os.path.join(self._root_dir,
947 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
948
Edward Lesmes07a68342021-04-20 23:39:30 +0000949 def _GetMirror(self, url, options, revision=None, revision_ref=None):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000950 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000951 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000952 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000953 mirror_kwargs = {
954 'print_func': self.filter,
Edward Lesmes07a68342021-04-20 23:39:30 +0000955 'refs': [],
956 'commits': [],
hinoka@google.comb1b54572014-04-16 22:29:23 +0000957 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000958 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
959 mirror_kwargs['refs'].append('refs/branch-heads/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000960 elif revision_ref and revision_ref.startswith('refs/branch-heads/'):
961 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000962 if hasattr(options, 'with_tags') and options.with_tags:
963 mirror_kwargs['refs'].append('refs/tags/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000964 elif revision_ref and revision_ref.startswith('refs/tags/'):
965 mirror_kwargs['refs'].append(revision_ref)
Edward Lesmes07a68342021-04-20 23:39:30 +0000966 if revision and not revision.startswith('refs/'):
967 mirror_kwargs['commits'].append(revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000968 return git_cache.Mirror(url, **mirror_kwargs)
969
John Budorick882c91e2018-07-12 22:11:41 +0000970 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800971 """Update a git mirror by fetching the latest commits from the remote,
972 unless mirror already contains revision whose type is sha1 hash.
973 """
John Budorick882c91e2018-07-12 22:11:41 +0000974 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800975 if options.verbose:
976 self.Print('skipping mirror update, it has rev=%s already' % revision,
977 timestamp=False)
978 return
979
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000980 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000981 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000982 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000983 depth = 10
984 else:
985 depth = 10000
986 else:
987 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000988 mirror.populate(verbose=options.verbose,
989 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000990 depth=depth,
Vadim Shtayura08049e22017-10-11 00:14:52 +0000991 lock_timeout=getattr(options, 'lock_timeout', 0))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000992
John Budorick882c91e2018-07-12 22:11:41 +0000993 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000994 """Clone a git repository from the given URL.
995
msb@chromium.org786fb682010-06-02 15:16:23 +0000996 Once we've cloned the repo, we checkout a working branch if the specified
997 revision is a branch head. If it is a tag or a specific commit, then we
998 leave HEAD detached as it makes future updates simpler -- in this case the
999 user should first create a new branch or switch to an existing branch before
1000 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001001 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001002 # git clone doesn't seem to insert a newline properly before printing
1003 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001004 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +00001005 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +00001006 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001007 if self.cache_dir:
1008 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001009 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001010 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001011 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +00001012 # If the parent directory does not exist, Git clone on Windows will not
1013 # create it, so we need to do it manually.
1014 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001015 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001016
1017 template_dir = None
1018 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +00001019 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001020 # In the case of a subproject, the pinned sha is not necessarily the
1021 # head of the remote branch (so we can't just use --depth=N). Instead,
1022 # we tell git to fetch all the remote objects from SHA..HEAD by means of
1023 # a template git dir which has a 'shallow' file pointing to the sha.
1024 template_dir = tempfile.mkdtemp(
1025 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
1026 dir=parent_dir)
1027 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
1028 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
1029 template_file.write(revision)
1030 clone_cmd.append('--template=' + template_dir)
1031 else:
1032 # Otherwise, we're just interested in the HEAD. Just use --depth.
1033 clone_cmd.append('--depth=1')
1034
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001035 tmp_dir = tempfile.mkdtemp(
1036 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
1037 dir=parent_dir)
1038 try:
1039 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001040 if self.print_outbuf:
1041 print_stdout = True
Edward Lemur24146be2019-08-01 21:44:52 +00001042 filter_fn = None
Edward Lemur231f5ea2018-01-31 19:02:36 +01001043 else:
1044 print_stdout = False
Edward Lemur24146be2019-08-01 21:44:52 +00001045 filter_fn = self.filter
Edward Lemur231f5ea2018-01-31 19:02:36 +01001046 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
Edward Lemur24146be2019-08-01 21:44:52 +00001047 print_stdout=print_stdout, filter_fn=filter_fn)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001048 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +00001049 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
1050 os.path.join(self.checkout_path, '.git'))
Edward Lesmesd4e20f22020-07-15 21:11:08 +00001051 # TODO(https://github.com/git-for-windows/git/issues/2569): Remove once
1052 # fixed.
1053 if sys.platform.startswith('win'):
1054 try:
1055 self._Run(['config', '--unset', 'core.worktree'], options,
1056 cwd=self.checkout_path)
1057 except subprocess2.CalledProcessError:
1058 pass
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001059 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001060 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001061 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001062 finally:
1063 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001064 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001065 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001066 if template_dir:
1067 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +00001068 self._SetFetchConfig(options)
1069 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001070 revision = self._AutoFetchRef(options, revision)
1071 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1072 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +00001073 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +00001074 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001075 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +00001076 ('Checked out %s to a detached HEAD. Before making any commits\n'
1077 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1078 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +00001079 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001080
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001081 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001082 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001083 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001084 raise gclient_utils.Error("Background task requires input. Rerun "
1085 "gclient with --jobs=1 so that\n"
1086 "interaction is possible.")
Edward Lesmesae3586b2020-03-23 21:21:14 +00001087 return gclient_utils.AskForData(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001088
1089
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001090 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001091 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001092 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001093 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001094 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001095 revision = upstream
1096 if newbase:
1097 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001098 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001099 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001100 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001101 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001102 printed_path = True
1103 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001104 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001105
1106 if merge:
1107 merge_output = self._Capture(['merge', revision])
1108 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001109 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001110 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001111
1112 # Build the rebase command here using the args
1113 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1114 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001115 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001116 rebase_cmd.append('--verbose')
1117 if newbase:
1118 rebase_cmd.extend(['--onto', newbase])
1119 rebase_cmd.append(upstream)
1120 if branch:
1121 rebase_cmd.append(branch)
1122
1123 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001124 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
Raul Tambreb946b232019-03-26 14:48:46 +00001125 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001126 if (re.match(br'cannot rebase: you have unstaged changes', e.stderr) or
1127 re.match(br'cannot rebase: your index contains uncommitted changes',
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001128 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001129 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001130 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001131 'Cannot rebase because of unstaged changes.\n'
1132 '\'git reset --hard HEAD\' ?\n'
1133 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001134 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001135 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001136 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001137 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001138 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001139 break
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +00001140 elif re.match(r'quit|q', rebase_action, re.I):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001141 raise gclient_utils.Error("Please merge or rebase manually\n"
1142 "cd %s && git " % self.checkout_path
1143 + "%s" % ' '.join(rebase_cmd))
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +00001144 elif re.match(r'show|s', rebase_action, re.I):
Edward Lemur979fa782019-08-13 22:44:05 +00001145 self.Print('%s' % e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001146 continue
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +00001147 else:
1148 gclient_utils.Error("Input not recognized")
1149 continue
Edward Lemur979fa782019-08-13 22:44:05 +00001150 elif re.search(br'^CONFLICT', e.stdout, re.M):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001151 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1152 "Fix the conflict and run gclient again.\n"
1153 "See 'man git-rebase' for details.\n")
1154 else:
Edward Lemur979fa782019-08-13 22:44:05 +00001155 self.Print(e.stdout.decode('utf-8').strip())
1156 self.Print('Rebase produced error output:\n%s' %
1157 e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001158 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1159 "manually.\ncd %s && git " %
1160 self.checkout_path
1161 + "%s" % ' '.join(rebase_cmd))
1162
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001163 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001164 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001165 # Make the output a little prettier. It's nice to have some
1166 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001167 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001168
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001169 @staticmethod
1170 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001171 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1172 if not ok:
1173 raise gclient_utils.Error('git version %s < minimum required %s' %
1174 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001175
John Budorick882c91e2018-07-12 22:11:41 +00001176 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001177 # Special case handling if all 3 conditions are met:
1178 # * the mirros have recently changed, but deps destination remains same,
1179 # * the git histories of mirrors are conflicting.
1180 # * git cache is used
1181 # This manifests itself in current checkout having invalid HEAD commit on
1182 # most git operations. Since git cache is used, just deleted the .git
1183 # folder, and re-create it by cloning.
1184 try:
1185 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1186 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001187 if (b'fatal: bad object HEAD' in e.stderr
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001188 and self.cache_dir and self.cache_dir in url):
1189 self.Print((
1190 'Likely due to DEPS change with git cache_dir, '
1191 'the current commit points to no longer existing object.\n'
1192 '%s' % e)
1193 )
1194 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001195 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001196 else:
1197 raise
1198
msb@chromium.org786fb682010-06-02 15:16:23 +00001199 def _IsRebasing(self):
1200 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1201 # have a plumbing command to determine whether a rebase is in progress, so
1202 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1203 g = os.path.join(self.checkout_path, '.git')
1204 return (
1205 os.path.isdir(os.path.join(g, "rebase-merge")) or
1206 os.path.isdir(os.path.join(g, "rebase-apply")))
1207
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001208 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001209 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1210 if os.path.exists(lockfile):
1211 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001212 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001213 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1214 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001215 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001216
msb@chromium.org786fb682010-06-02 15:16:23 +00001217 # Make sure the tree is clean; see git-rebase.sh for reference
1218 try:
1219 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001220 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001221 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001222 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001223 '\tYou have unstaged changes.\n'
1224 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001225 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001226 try:
1227 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001228 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001229 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001230 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001231 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001232 '\tYour index contains uncommitted changes\n'
1233 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001234 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001235
agable83faed02016-10-24 14:37:10 -07001236 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001237 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1238 # reference by a commit). If not, error out -- most likely a rebase is
1239 # in progress, try to detect so we can give a better error.
1240 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001241 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1242 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001243 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001244 # Commit is not contained by any rev. See if the user is rebasing:
1245 if self._IsRebasing():
1246 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001247 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001248 '\tAlready in a conflict, i.e. (no branch).\n'
1249 '\tFix the conflict and run gclient again.\n'
1250 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1251 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001252 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001253 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001254 name = ('saved-by-gclient-' +
1255 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001256 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001257 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001258 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001259
msb@chromium.org5bde4852009-12-14 16:47:12 +00001260 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001261 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001262 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001263 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001264 return None
1265 return branch
1266
borenet@google.comc3e09d22014-04-10 13:58:18 +00001267 def _Capture(self, args, **kwargs):
Mike Frysinger286fb162019-09-30 03:14:10 +00001268 set_git_dir = 'cwd' not in kwargs
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001269 kwargs.setdefault('cwd', self.checkout_path)
1270 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001271 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001272 env = scm.GIT.ApplyEnvVars(kwargs)
Mike Frysinger286fb162019-09-30 03:14:10 +00001273 # If an explicit cwd isn't set, then default to the .git/ subdir so we get
1274 # stricter behavior. This can be useful in cases of slight corruption --
1275 # we don't accidentally go corrupting parent git checks too. See
1276 # https://crbug.com/1000825 for an example.
1277 if set_git_dir:
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001278 git_dir = os.path.abspath(os.path.join(self.checkout_path, '.git'))
Dirk Prankedb1e79c2019-10-23 01:46:32 +00001279 # Depending on how the .gclient file was defined, self.checkout_path
1280 # might be set to a unicode string, not a regular string; on Windows
1281 # Python2, we can't set env vars to be unicode strings, so we
1282 # forcibly cast the value to a string before setting it.
1283 env.setdefault('GIT_DIR', str(git_dir))
Raul Tambrecd862e32019-05-10 21:19:00 +00001284 ret = subprocess2.check_output(
1285 ['git'] + args, env=env, **kwargs).decode('utf-8')
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001286 if strip:
1287 ret = ret.strip()
Erik Chene16ffff2019-10-14 20:35:53 +00001288 self.Print('Finished running: %s %s' % ('git', ' '.join(args)))
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001289 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001290
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001291 def _Checkout(self, options, ref, force=False, quiet=None):
1292 """Performs a 'git-checkout' operation.
1293
1294 Args:
1295 options: The configured option set
1296 ref: (str) The branch/commit to checkout
Quinten Yearsley925cedb2020-04-13 17:49:39 +00001297 quiet: (bool/None) Whether or not the checkout should pass '--quiet'; if
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001298 'None', the behavior is inferred from 'options.verbose'.
1299 Returns: (str) The output of the checkout operation
1300 """
1301 if quiet is None:
1302 quiet = (not options.verbose)
1303 checkout_args = ['checkout']
1304 if force:
1305 checkout_args.append('--force')
1306 if quiet:
1307 checkout_args.append('--quiet')
1308 checkout_args.append(ref)
1309 return self._Capture(checkout_args)
1310
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001311 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1312 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001313 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemur9a5e3bd2019-04-02 23:37:45 +00001314 # When updating, the ref is modified to be a remote ref .
1315 # (e.g. refs/heads/NAME becomes refs/remotes/REMOTE/NAME).
1316 # Try to reverse that mapping.
1317 original_ref = scm.GIT.RemoteRefToRef(refspec, self.remote)
1318 if original_ref:
1319 refspec = original_ref + ':' + refspec
1320 # When a mirror is configured, it only fetches
1321 # refs/{heads,branch-heads,tags}/*.
1322 # If asked to fetch other refs, we must fetch those directly from the
1323 # repository, and not from the mirror.
1324 if not original_ref.startswith(
1325 ('refs/heads/', 'refs/branch-heads/', 'refs/tags/')):
1326 remote, _ = gclient_utils.SplitUrlRevision(self.url)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001327 fetch_cmd = cfg + [
1328 'fetch',
1329 remote or self.remote,
1330 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001331 if refspec:
1332 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001333
1334 if prune:
1335 fetch_cmd.append('--prune')
1336 if options.verbose:
1337 fetch_cmd.append('--verbose')
danakjd5c0b562019-11-08 17:27:47 +00001338 if not hasattr(options, 'with_tags') or not options.with_tags:
1339 fetch_cmd.append('--no-tags')
dnj@chromium.org680f2172014-06-25 00:39:32 +00001340 elif quiet:
1341 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001342 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001343
Edward Lemur579c9862018-07-13 23:17:51 +00001344 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001345 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1346 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001347 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001348 try:
1349 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1350 options)
1351 self._Run(['config', 'remote.%s.fetch' % self.remote,
1352 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1353 except subprocess2.CalledProcessError as e:
1354 # If exit code was 5, it means we attempted to unset a config that
1355 # didn't exist. Ignore it.
1356 if e.returncode != 5:
1357 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001358 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001359 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001360 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1361 '^\\+refs/branch-heads/\\*:.*$']
1362 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001363 if hasattr(options, 'with_tags') and options.with_tags:
1364 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1365 '+refs/tags/*:refs/tags/*',
1366 '^\\+refs/tags/\\*:.*$']
1367 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001368
John Budorick882c91e2018-07-12 22:11:41 +00001369 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001370 """Attempts to fetch |revision| if not available in local repo.
1371
1372 Returns possibly updated revision."""
Edward Lemure0ba7b82020-03-11 20:31:32 +00001373 if not scm.GIT.IsValidRevision(self.checkout_path, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001374 self._Fetch(options, refspec=revision)
1375 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1376 return revision
1377
Edward Lemur24146be2019-08-01 21:44:52 +00001378 def _Run(self, args, options, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001379 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001380 kwargs.setdefault('cwd', self.checkout_path)
Edward Lemur24146be2019-08-01 21:44:52 +00001381 kwargs.setdefault('filter_fn', self.filter)
1382 kwargs.setdefault('show_header', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001383 env = scm.GIT.ApplyEnvVars(kwargs)
Nico Weberc49c88a2020-07-08 17:36:02 +00001384
agable@chromium.org772efaf2014-04-01 02:35:44 +00001385 cmd = ['git'] + args
Edward Lemur24146be2019-08-01 21:44:52 +00001386 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001387
1388
1389class CipdPackage(object):
1390 """A representation of a single CIPD package."""
1391
John Budorickd3ba72b2018-03-20 12:27:42 -07001392 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001393 self._authority_for_subdir = authority_for_subdir
1394 self._name = name
1395 self._version = version
1396
1397 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001398 def authority_for_subdir(self):
1399 """Whether this package has authority to act on behalf of its subdir.
1400
1401 Some operations should only be performed once per subdirectory. A package
1402 that has authority for its subdirectory is the only package that should
1403 perform such operations.
1404
1405 Returns:
1406 bool; whether this package has subdir authority.
1407 """
1408 return self._authority_for_subdir
1409
1410 @property
1411 def name(self):
1412 return self._name
1413
1414 @property
1415 def version(self):
1416 return self._version
1417
1418
1419class CipdRoot(object):
1420 """A representation of a single CIPD root."""
1421 def __init__(self, root_dir, service_url):
1422 self._all_packages = set()
1423 self._mutator_lock = threading.Lock()
1424 self._packages_by_subdir = collections.defaultdict(list)
1425 self._root_dir = root_dir
1426 self._service_url = service_url
1427
1428 def add_package(self, subdir, package, version):
1429 """Adds a package to this CIPD root.
1430
1431 As far as clients are concerned, this grants both root and subdir authority
1432 to packages arbitrarily. (The implementation grants root authority to the
1433 first package added and subdir authority to the first package added for that
1434 subdir, but clients should not depend on or expect that behavior.)
1435
1436 Args:
1437 subdir: str; relative path to where the package should be installed from
1438 the cipd root directory.
1439 package: str; the cipd package name.
1440 version: str; the cipd package version.
1441 Returns:
1442 CipdPackage; the package that was created and added to this root.
1443 """
1444 with self._mutator_lock:
1445 cipd_package = CipdPackage(
1446 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001447 not self._packages_by_subdir[subdir])
1448 self._all_packages.add(cipd_package)
1449 self._packages_by_subdir[subdir].append(cipd_package)
1450 return cipd_package
1451
1452 def packages(self, subdir):
1453 """Get the list of configured packages for the given subdir."""
1454 return list(self._packages_by_subdir[subdir])
1455
1456 def clobber(self):
1457 """Remove the .cipd directory.
1458
1459 This is useful for forcing ensure to redownload and reinitialize all
1460 packages.
1461 """
1462 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001463 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001464 try:
1465 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1466 except OSError:
1467 if os.path.exists(cipd_cache_dir):
1468 raise
1469
1470 @contextlib.contextmanager
1471 def _create_ensure_file(self):
1472 try:
Edward Lesmes05934952019-12-19 20:38:09 +00001473 contents = '$ParanoidMode CheckPresence\n\n'
1474 for subdir, packages in sorted(self._packages_by_subdir.items()):
1475 contents += '@Subdir %s\n' % subdir
1476 for package in sorted(packages, key=lambda p: p.name):
1477 contents += '%s %s\n' % (package.name, package.version)
1478 contents += '\n'
John Budorick0f7b2002018-01-19 15:46:17 -08001479 ensure_file = None
1480 with tempfile.NamedTemporaryFile(
Edward Lesmes05934952019-12-19 20:38:09 +00001481 suffix='.ensure', delete=False, mode='wb') as ensure_file:
1482 ensure_file.write(contents.encode('utf-8', 'replace'))
John Budorick0f7b2002018-01-19 15:46:17 -08001483 yield ensure_file.name
1484 finally:
1485 if ensure_file is not None and os.path.exists(ensure_file.name):
1486 os.remove(ensure_file.name)
1487
1488 def ensure(self):
1489 """Run `cipd ensure`."""
1490 with self._mutator_lock:
1491 with self._create_ensure_file() as ensure_file:
1492 cmd = [
1493 'cipd', 'ensure',
1494 '-log-level', 'error',
1495 '-root', self.root_dir,
1496 '-ensure-file', ensure_file,
1497 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001498 gclient_utils.CheckCallAndFilter(
1499 cmd, print_stdout=True, show_header=True)
John Budorick0f7b2002018-01-19 15:46:17 -08001500
John Budorickd3ba72b2018-03-20 12:27:42 -07001501 def run(self, command):
1502 if command == 'update':
1503 self.ensure()
1504 elif command == 'revert':
1505 self.clobber()
1506 self.ensure()
1507
John Budorick0f7b2002018-01-19 15:46:17 -08001508 def created_package(self, package):
1509 """Checks whether this root created the given package.
1510
1511 Args:
1512 package: CipdPackage; the package to check.
1513 Returns:
1514 bool; whether this root created the given package.
1515 """
1516 return package in self._all_packages
1517
1518 @property
1519 def root_dir(self):
1520 return self._root_dir
1521
1522 @property
1523 def service_url(self):
1524 return self._service_url
1525
1526
1527class CipdWrapper(SCMWrapper):
1528 """Wrapper for CIPD.
1529
1530 Currently only supports chrome-infra-packages.appspot.com.
1531 """
John Budorick3929e9e2018-02-04 18:18:07 -08001532 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001533
1534 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1535 out_cb=None, root=None, package=None):
1536 super(CipdWrapper, self).__init__(
1537 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1538 out_cb=out_cb)
1539 assert root.created_package(package)
1540 self._package = package
1541 self._root = root
1542
1543 #override
1544 def GetCacheMirror(self):
1545 return None
1546
1547 #override
1548 def GetActualRemoteURL(self, options):
1549 return self._root.service_url
1550
1551 #override
1552 def DoesRemoteURLMatch(self, options):
1553 del options
1554 return True
1555
1556 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001557 """Does nothing.
1558
1559 CIPD packages should be reverted at the root by running
1560 `CipdRoot.run('revert')`.
1561 """
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +00001562 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001563
1564 def diff(self, options, args, file_list):
1565 """CIPD has no notion of diffing."""
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +00001566 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001567
1568 def pack(self, options, args, file_list):
1569 """CIPD has no notion of diffing."""
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +00001570 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001571
1572 def revinfo(self, options, args, file_list):
1573 """Grab the instance ID."""
1574 try:
1575 tmpdir = tempfile.mkdtemp()
1576 describe_json_path = os.path.join(tmpdir, 'describe.json')
1577 cmd = [
1578 'cipd', 'describe',
1579 self._package.name,
1580 '-log-level', 'error',
1581 '-version', self._package.version,
1582 '-json-output', describe_json_path
1583 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001584 gclient_utils.CheckCallAndFilter(cmd)
John Budorick0f7b2002018-01-19 15:46:17 -08001585 with open(describe_json_path) as f:
1586 describe_json = json.load(f)
1587 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1588 finally:
1589 gclient_utils.rmtree(tmpdir)
1590
1591 def status(self, options, args, file_list):
1592 pass
1593
1594 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001595 """Does nothing.
1596
1597 CIPD packages should be updated at the root by running
1598 `CipdRoot.run('update')`.
1599 """
Josip Sokcevic42c5bbb2022-01-24 21:42:28 +00001600 pass