blob: d599bfef31da1e9d492a1773d594432b474e9082 [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
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000015import posixpath
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000016import re
maruel@chromium.org90541732011-04-01 17:54:18 +000017import sys
ilevy@chromium.org3534aa52013-07-20 01:58:08 +000018import tempfile
John Budorick0f7b2002018-01-19 15:46:17 -080019import threading
zty@chromium.org6279e8a2014-02-13 01:45:25 +000020import traceback
Raul Tambreb946b232019-03-26 14:48:46 +000021
22try:
23 import urlparse
24except ImportError: # For Py3 compatibility
25 import urllib.parse as urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000026
hinoka@google.com2f2ca142014-01-07 03:59:18 +000027import download_from_google_storage
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000028import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +000029import git_cache
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000030import scm
borenet@google.comb2256212014-05-07 20:57:28 +000031import shutil
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000032import subprocess2
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000033
34
szager@chromium.org71cbb502013-04-19 23:30:15 +000035THIS_FILE_PATH = os.path.abspath(__file__)
36
hinoka@google.com2f2ca142014-01-07 03:59:18 +000037GSUTIL_DEFAULT_PATH = os.path.join(
hinoka@chromium.orgb091aa52014-12-20 01:47:31 +000038 os.path.dirname(os.path.abspath(__file__)), 'gsutil.py')
hinoka@google.com2f2ca142014-01-07 03:59:18 +000039
maruel@chromium.org79d62372015-06-01 18:50:55 +000040
smutae7ea312016-07-18 11:59:41 -070041class NoUsableRevError(gclient_utils.Error):
42 """Raised if requested revision isn't found in checkout."""
43
44
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000045class DiffFiltererWrapper(object):
46 """Simple base class which tracks which file is being diffed and
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000047 replaces instances of its file name in the original and
agable41e3a6c2016-10-20 11:36:56 -070048 working copy lines of the git diff output."""
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000049 index_string = None
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000050 original_prefix = "--- "
51 working_prefix = "+++ "
52
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000053 def __init__(self, relpath, print_func):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000054 # Note that we always use '/' as the path separator to be
agable41e3a6c2016-10-20 11:36:56 -070055 # consistent with cygwin-style output on Windows
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000056 self._relpath = relpath.replace("\\", "/")
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000057 self._current_file = None
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000058 self._print_func = print_func
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000059
maruel@chromium.org6e29d572010-06-04 17:32:20 +000060 def SetCurrentFile(self, current_file):
61 self._current_file = current_file
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000062
iannucci@chromium.org3830a672013-02-19 20:15:14 +000063 @property
64 def _replacement_file(self):
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000065 return posixpath.join(self._relpath, self._current_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000066
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000067 def _Replace(self, line):
68 return line.replace(self._current_file, self._replacement_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000069
70 def Filter(self, line):
71 if (line.startswith(self.index_string)):
72 self.SetCurrentFile(line[len(self.index_string):])
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000073 line = self._Replace(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000074 else:
75 if (line.startswith(self.original_prefix) or
76 line.startswith(self.working_prefix)):
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000077 line = self._Replace(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000078 self._print_func(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000079
80
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000081class GitDiffFilterer(DiffFiltererWrapper):
82 index_string = "diff --git "
83
84 def SetCurrentFile(self, current_file):
85 # Get filename by parsing "a/<filename> b/<filename>"
86 self._current_file = current_file[:(len(current_file)/2)][2:]
87
88 def _Replace(self, line):
89 return re.sub("[a|b]/" + self._current_file, self._replacement_file, line)
90
91
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000092# SCMWrapper base class
93
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000094class SCMWrapper(object):
95 """Add necessary glue between all the supported SCM.
96
msb@chromium.orgd6504212010-01-13 17:34:31 +000097 This is the abstraction layer to bind to different SCM.
98 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000099 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
Edward Lemur231f5ea2018-01-31 19:02:36 +0100100 out_cb=None, print_outbuf=False):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000101 self.url = url
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +0000102 self._root_dir = root_dir
103 if self._root_dir:
104 self._root_dir = self._root_dir.replace('/', os.sep)
105 self.relpath = relpath
106 if self.relpath:
107 self.relpath = self.relpath.replace('/', os.sep)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000108 if self.relpath and self._root_dir:
109 self.checkout_path = os.path.join(self._root_dir, self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000110 if out_fh is None:
111 out_fh = sys.stdout
112 self.out_fh = out_fh
113 self.out_cb = out_cb
Edward Lemur231f5ea2018-01-31 19:02:36 +0100114 self.print_outbuf = print_outbuf
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000115
116 def Print(self, *args, **kwargs):
117 kwargs.setdefault('file', self.out_fh)
118 if kwargs.pop('timestamp', True):
119 self.out_fh.write('[%s] ' % gclient_utils.Elapsed())
120 print(*args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000121
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000122 def RunCommand(self, command, options, args, file_list=None):
agabledebf6c82016-12-21 12:50:12 -0800123 commands = ['update', 'updatesingle', 'revert',
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000124 'revinfo', 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000125
126 if not command in commands:
127 raise gclient_utils.Error('Unknown command %s' % command)
128
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000129 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000130 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000131 command, self.__class__.__name__))
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000132
133 return getattr(self, command)(options, args, file_list)
134
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000135 @staticmethod
136 def _get_first_remote_url(checkout_path):
137 log = scm.GIT.Capture(
138 ['config', '--local', '--get-regexp', r'remote.*.url'],
139 cwd=checkout_path)
140 # Get the second token of the first line of the log.
141 return log.splitlines()[0].split(' ', 1)[1]
142
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000143 def GetCacheMirror(self):
Robert Iannuccia19649b2018-06-29 16:31:45 +0000144 if getattr(self, 'cache_dir', None):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000145 url, _ = gclient_utils.SplitUrlRevision(self.url)
146 return git_cache.Mirror(url)
147 return None
148
smut@google.comd33eab32014-07-07 19:35:18 +0000149 def GetActualRemoteURL(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000150 """Attempt to determine the remote URL for this SCMWrapper."""
smut@google.comd33eab32014-07-07 19:35:18 +0000151 # Git
borenet@google.combda475e2014-03-24 19:04:45 +0000152 if os.path.exists(os.path.join(self.checkout_path, '.git')):
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000153 actual_remote_url = self._get_first_remote_url(self.checkout_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000154
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000155 mirror = self.GetCacheMirror()
156 # If the cache is used, obtain the actual remote URL from there.
157 if (mirror and mirror.exists() and
158 mirror.mirror_path.replace('\\', '/') ==
159 actual_remote_url.replace('\\', '/')):
160 actual_remote_url = self._get_first_remote_url(mirror.mirror_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000161 return actual_remote_url
borenet@google.com88d10082014-03-21 17:24:48 +0000162 return None
163
borenet@google.com4e9be262014-04-08 19:40:30 +0000164 def DoesRemoteURLMatch(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000165 """Determine whether the remote URL of this checkout is the expected URL."""
166 if not os.path.exists(self.checkout_path):
167 # A checkout which doesn't exist can't be broken.
168 return True
169
smut@google.comd33eab32014-07-07 19:35:18 +0000170 actual_remote_url = self.GetActualRemoteURL(options)
borenet@google.com88d10082014-03-21 17:24:48 +0000171 if actual_remote_url:
borenet@google.com8156c9f2014-04-01 16:41:36 +0000172 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
173 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
borenet@google.com88d10082014-03-21 17:24:48 +0000174 else:
175 # This may occur if the self.checkout_path exists but does not contain a
agable41e3a6c2016-10-20 11:36:56 -0700176 # valid git checkout.
borenet@google.com88d10082014-03-21 17:24:48 +0000177 return False
178
borenet@google.comb09097a2014-04-09 19:09:08 +0000179 def _DeleteOrMove(self, force):
180 """Delete the checkout directory or move it out of the way.
181
182 Args:
183 force: bool; if True, delete the directory. Otherwise, just move it.
184 """
borenet@google.comb2256212014-05-07 20:57:28 +0000185 if force and os.environ.get('CHROME_HEADLESS') == '1':
186 self.Print('_____ Conflicting directory found in %s. Removing.'
187 % self.checkout_path)
188 gclient_utils.AddWarning('Conflicting directory %s deleted.'
189 % self.checkout_path)
190 gclient_utils.rmtree(self.checkout_path)
191 else:
192 bad_scm_dir = os.path.join(self._root_dir, '_bad_scm',
193 os.path.dirname(self.relpath))
194
195 try:
196 os.makedirs(bad_scm_dir)
197 except OSError as e:
198 if e.errno != errno.EEXIST:
199 raise
200
201 dest_path = tempfile.mkdtemp(
202 prefix=os.path.basename(self.relpath),
203 dir=bad_scm_dir)
204 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
205 % (self.checkout_path, dest_path))
206 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
207 % (self.checkout_path, dest_path))
208 shutil.move(self.checkout_path, dest_path)
borenet@google.comb09097a2014-04-09 19:09:08 +0000209
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000210
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000211class GitWrapper(SCMWrapper):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000212 """Wrapper for Git"""
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000213 name = 'git'
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000214 remote = 'origin'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000215
Robert Iannuccia19649b2018-06-29 16:31:45 +0000216 @property
217 def cache_dir(self):
218 try:
219 return git_cache.Mirror.GetCachePath()
220 except RuntimeError:
221 return None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000222
John Budorick0f7b2002018-01-19 15:46:17 -0800223 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000224 """Removes 'git+' fake prefix from git URL."""
Henrique Ferreiroe72279d2019-04-17 12:01:50 +0000225 if url and (url.startswith('git+http://') or
226 url.startswith('git+https://')):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000227 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800228 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000229 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
230 if self.out_cb:
231 filter_kwargs['predicate'] = self.out_cb
232 self.filter = gclient_utils.GitFilter(**filter_kwargs)
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000233
mukai@chromium.org9e3e82c2012-04-18 12:55:43 +0000234 @staticmethod
235 def BinaryExists():
236 """Returns true if the command exists."""
237 try:
238 # We assume git is newer than 1.7. See: crbug.com/114483
239 result, version = scm.GIT.AssertVersion('1.7')
240 if not result:
241 raise gclient_utils.Error('Git version is older than 1.7: %s' % version)
242 return result
243 except OSError:
244 return False
245
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000246 def GetCheckoutRoot(self):
247 return scm.GIT.GetCheckoutRoot(self.checkout_path)
248
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000249 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000250 """Returns the given revision's date in ISO-8601 format (which contains the
251 time zone)."""
252 # TODO(floitsch): get the time-stamp of the given revision and not just the
253 # time-stamp of the currently checked out revision.
254 return self._Capture(['log', '-n', '1', '--format=%ai'])
255
Aaron Gablef4068aa2017-12-12 15:14:09 -0800256 def _GetDiffFilenames(self, base):
257 """Returns the names of files modified since base."""
258 return self._Capture(
Raul Tambrecd862e32019-05-10 21:19:00 +0000259 # Filter to remove base if it is None.
260 list(filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only',
261 base])
262 )).split()
Aaron Gablef4068aa2017-12-12 15:14:09 -0800263
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000264 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 15:45:56 -0800265 _, revision = gclient_utils.SplitUrlRevision(self.url)
266 if not revision:
267 revision = 'refs/remotes/%s/master' % self.remote
268 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000269
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000270 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000271 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000272 repository.
273
274 The patch file is generated from a diff of the merge base of HEAD and
275 its upstream branch.
276 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700277 try:
278 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
279 except subprocess2.CalledProcessError:
280 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000281 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700282 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000283 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000284 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000285
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800286 def _Scrub(self, target, options):
287 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000288 quiet = []
289 if not options.verbose:
290 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800291 self._Run(['reset', '--hard', target] + quiet, options)
292 if options.force and options.delete_unversioned_trees:
293 # where `target` is a commit that contains both upper and lower case
294 # versions of the same file on a case insensitive filesystem, we are
295 # actually in a broken state here. The index will have both 'a' and 'A',
296 # but only one of them will exist on the disk. To progress, we delete
297 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800298 output = self._Capture([
299 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800300 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800301 # --porcelain (v1) looks like:
302 # XY filename
303 try:
304 filename = line[3:]
305 self.Print('_____ Deleting residual after reset: %r.' % filename)
306 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800307 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800308 except OSError:
309 pass
310
John Budorick882c91e2018-07-12 22:11:41 +0000311 def _FetchAndReset(self, revision, file_list, options):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800312 """Equivalent to git fetch; git reset."""
Edward Lemur579c9862018-07-13 23:17:51 +0000313 self._SetFetchConfig(options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000314
dnj@chromium.org680f2172014-06-25 00:39:32 +0000315 self._Fetch(options, prune=True, quiet=options.verbose)
John Budorick882c91e2018-07-12 22:11:41 +0000316 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000317 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800318 files = self._Capture(
319 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambreb946b232019-03-26 14:48:46 +0000320 file_list.extend(
321 [os.path.join(self.checkout_path, f.decode()) for f in files])
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000322
szager@chromium.org8a139702014-06-20 15:55:01 +0000323 def _DisableHooks(self):
324 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
325 if not os.path.isdir(hook_dir):
326 return
327 for f in os.listdir(hook_dir):
328 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000329 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
330 if os.path.exists(disabled_hook_path):
331 os.remove(disabled_hook_path)
332 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000333
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000334 def _maybe_break_locks(self, options):
335 """This removes all .lock files from this repo's .git directory, if the
336 user passed the --break_repo_locks command line flag.
337
338 In particular, this will cleanup index.lock files, as well as ref lock
339 files.
340 """
341 if options.break_repo_locks:
342 git_dir = os.path.join(self.checkout_path, '.git')
343 for path, _, filenames in os.walk(git_dir):
344 for filename in filenames:
345 if filename.endswith('.lock'):
346 to_break = os.path.join(path, filename)
347 self.Print('breaking lock: %s' % (to_break,))
348 try:
349 os.remove(to_break)
350 except OSError as ex:
351 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
352 raise
353
Edward Lemur3acbc742019-05-30 17:57:35 +0000354 def apply_patch_ref(self, patch_repo, patch_rev, target_rev, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000355 file_list):
356 """Apply a patch on top of the revision we're synced at.
357
Edward Lemur3acbc742019-05-30 17:57:35 +0000358 The patch ref is given by |patch_repo|@|patch_rev|.
359 |target_rev| is usually the branch that the |patch_rev| was uploaded against
360 (e.g. 'refs/heads/master'), but this is not required.
361
362 We cherry-pick all commits reachable from |patch_rev| on top of the curret
363 HEAD, excluding those reachable from |target_rev|
364 (i.e. git cherry-pick target_rev..patch_rev).
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000365
366 Graphically, it looks like this:
367
Edward Lemur3acbc742019-05-30 17:57:35 +0000368 ... -> o -> [possibly already landed commits] -> target_rev
369 \
370 -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000371
Edward Lemur3acbc742019-05-30 17:57:35 +0000372 The final checkout state is then:
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000373
Edward Lemur3acbc742019-05-30 17:57:35 +0000374 ... -> HEAD -> [possibly not yet landed dependent CLs] -> patch_rev
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000375
376 After application, if |options.reset_patch_ref| is specified, we soft reset
Edward Lemur3acbc742019-05-30 17:57:35 +0000377 the cherry-picked changes, keeping them in git index only.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000378
379 Args:
Edward Lemur3acbc742019-05-30 17:57:35 +0000380 patch_repo: The patch origin.
381 e.g. 'https://foo.googlesource.com/bar'
382 patch_rev: The revision to patch.
383 e.g. 'refs/changes/1234/34/1'.
384 target_rev: The revision to use when finding the merge base.
385 Typically, the branch that the patch was uploaded against.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000386 e.g. 'refs/heads/master' or 'refs/heads/infra/config'.
387 options: The options passed to gclient.
388 file_list: A list where modified files will be appended.
389 """
390
Edward Lemurca7d8812018-07-24 17:42:45 +0000391 # Abort any cherry-picks in progress.
392 try:
393 self._Capture(['cherry-pick', '--abort'])
394 except subprocess2.CalledProcessError:
395 pass
396
Edward Lesmesc621b212018-03-21 20:26:56 -0400397 base_rev = self._Capture(['rev-parse', 'HEAD'])
Edward Lemura0ffbe42019-05-01 16:52:18 +0000398
Edward Lemur3acbc742019-05-30 17:57:35 +0000399 if not target_rev:
Edward Lemur4c5c8ab2019-06-07 15:58:13 +0000400 raise gclient_utils.Error('A target revision for the patch must be given')
Edward Lemur3acbc742019-05-30 17:57:35 +0000401 elif target_rev.startswith('refs/heads/'):
402 # If |target_rev| is in refs/heads/**, try first to find the corresponding
403 # remote ref for it, since |target_rev| might point to a local ref which
404 # is not up to date with the corresponding remote ref.
405 remote_ref = ''.join(scm.GIT.RefToRemoteRef(target_rev, self.remote))
406 self.Print('Trying the correspondig remote ref for %r: %r\n' % (
407 target_rev, remote_ref))
408 if scm.GIT.IsValidRevision(self.checkout_path, remote_ref):
409 target_rev = remote_ref
410 elif not scm.GIT.IsValidRevision(self.checkout_path, target_rev):
411 # Fetch |target_rev| if it's not already available.
412 url, _ = gclient_utils.SplitUrlRevision(self.url)
413 mirror = self._GetMirror(url, options, target_rev)
414 if mirror:
415 rev_type = 'branch' if target_rev.startswith('refs/') else 'hash'
416 self._UpdateMirrorIfNotContains(mirror, options, rev_type, target_rev)
417 self._Fetch(options, refspec=target_rev)
Edward Lemura0ffbe42019-05-01 16:52:18 +0000418
Edward Lemur3acbc742019-05-30 17:57:35 +0000419 self.Print('===Applying patch===')
420 self.Print('Revision to patch is %r @ %r.' % (patch_repo, patch_rev))
421 self.Print('Will cherrypick %r .. %r on top of %r.' % (
422 target_rev, patch_rev, base_rev))
423 self.Print('Current dir is %r' % self.checkout_path)
Edward Lesmesc621b212018-03-21 20:26:56 -0400424 self._Capture(['reset', '--hard'])
Edward Lemur3acbc742019-05-30 17:57:35 +0000425 self._Capture(['fetch', patch_repo, patch_rev])
Edward Lemurca7d8812018-07-24 17:42:45 +0000426 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400427
Edward Lemur3acbc742019-05-30 17:57:35 +0000428 if not options.rebase_patch_ref:
429 self._Capture(['checkout', patch_rev])
430 else:
431 try:
432 if scm.GIT.IsAncestor(self.checkout_path, patch_rev, target_rev):
433 # If |patch_rev| is an ancestor of |target_rev|, check it out.
Edward Lemurca7d8812018-07-24 17:42:45 +0000434 self._Capture(['checkout', patch_rev])
435 else:
436 # If a change was uploaded on top of another change, which has already
437 # landed, one of the commits in the cherry-pick range will be
438 # redundant, since it has already landed and its changes incorporated
439 # in the tree.
440 # We pass '--keep-redundant-commits' to ignore those changes.
Edward Lemur3acbc742019-05-30 17:57:35 +0000441 self._Capture(['cherry-pick', target_rev + '..' + patch_rev,
Edward Lemurca7d8812018-07-24 17:42:45 +0000442 '--keep-redundant-commits'])
443
Edward Lemur3acbc742019-05-30 17:57:35 +0000444 except subprocess2.CalledProcessError as e:
445 self.Print('Failed to apply patch.')
446 self.Print('Revision to patch was %r @ %r.' % (patch_repo, patch_rev))
447 self.Print('Tried to cherrypick %r .. %r on top of %r.' % (
448 target_rev, patch_rev, base_rev))
449 self.Print('Current dir is %r' % self.checkout_path)
450 self.Print('git returned non-zero exit status %s:\n%s' % (
Edward Lemur979fa782019-08-13 22:44:05 +0000451 e.returncode, e.stderr.decode('utf-8')))
Edward Lemur3acbc742019-05-30 17:57:35 +0000452 # Print the current status so that developers know what changes caused
453 # the patch failure, since git cherry-pick doesn't show that
454 # information.
455 self.Print(self._Capture(['status']))
456 try:
457 self._Capture(['cherry-pick', '--abort'])
458 except subprocess2.CalledProcessError:
459 pass
460 raise
Edward Lemurca7d8812018-07-24 17:42:45 +0000461
Edward Lemur3acbc742019-05-30 17:57:35 +0000462 if file_list is not None:
463 file_list.extend(self._GetDiffFilenames(base_rev))
Edward Lemurca7d8812018-07-24 17:42:45 +0000464
Edward Lesmesc621b212018-03-21 20:26:56 -0400465 if options.reset_patch_ref:
466 self._Capture(['reset', '--soft', base_rev])
467
msb@chromium.orge28e4982009-09-25 20:51:45 +0000468 def update(self, options, args, file_list):
469 """Runs git to update or transparently checkout the working copy.
470
471 All updated files will be appended to file_list.
472
473 Raises:
474 Error: if can't get URL for relative path.
475 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000476 if args:
477 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
478
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000479 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000480
John Budorick882c91e2018-07-12 22:11:41 +0000481 # If a dependency is not pinned, track the default remote branch.
482 default_rev = 'refs/remotes/%s/master' % self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000483 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000484 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000485 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000486 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000487 # Override the revision number.
488 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000489 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000490 # Check again for a revision in case an initial ref was specified
491 # in the url, for example bla.git@refs/heads/custombranch
492 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000493 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000494 if not revision:
495 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000496
szager@chromium.org8a139702014-06-20 15:55:01 +0000497 if managed:
498 self._DisableHooks()
499
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000500 printed_path = False
501 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000502 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700503 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000504 verbose = ['--verbose']
505 printed_path = True
506
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000507 revision_ref = revision
508 if ':' in revision:
509 revision_ref, _, revision = revision.partition(':')
510
511 mirror = self._GetMirror(url, options, revision_ref)
512 if mirror:
513 url = mirror.mirror_path
Edward Lemurdb5c5ad2019-03-07 16:00:24 +0000514
John Budorick882c91e2018-07-12 22:11:41 +0000515 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
516 if remote_ref:
517 # Rewrite remote refs to their local equivalents.
518 revision = ''.join(remote_ref)
519 rev_type = "branch"
520 elif revision.startswith('refs/'):
521 # Local branch? We probably don't want to support, since DEPS should
522 # always specify branches as they are in the upstream repo.
523 rev_type = "branch"
524 else:
525 # hash is also a tag, only make a distinction at checkout
526 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000527
primiano@chromium.org1c127382015-02-17 11:15:40 +0000528 # If we are going to introduce a new project, there is a possibility that
529 # we are syncing back to a state where the project was originally a
530 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
531 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
532 # In such case, we might have a backup of the former .git folder, which can
533 # be used to avoid re-fetching the entire repo again (useful for bisects).
534 backup_dir = self.GetGitBackupDirPath()
535 target_dir = os.path.join(self.checkout_path, '.git')
536 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
537 gclient_utils.safe_makedirs(self.checkout_path)
538 os.rename(backup_dir, target_dir)
539 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800540 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000541
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000542 if (not os.path.exists(self.checkout_path) or
543 (os.path.isdir(self.checkout_path) and
544 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000545 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000546 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000547 try:
John Budorick882c91e2018-07-12 22:11:41 +0000548 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000549 except subprocess2.CalledProcessError:
550 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000551 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000552 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800553 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20 +0000554 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambrec2f74c12019-03-19 05:55:53 +0000555 file_list.extend(
Edward Lemur979fa782019-08-13 22:44:05 +0000556 [os.path.join(self.checkout_path, f) for f in files])
John Budorick21a51b32018-09-19 19:39:20 +0000557 if mirror:
558 self._Capture(
559 ['remote', 'set-url', '--push', 'origin', mirror.url])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000560 if not verbose:
561 # Make the output a little prettier. It's nice to have some whitespace
562 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000563 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000564 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000565
John Budorick21a51b32018-09-19 19:39:20 +0000566 if mirror:
567 self._Capture(
568 ['remote', 'set-url', '--push', 'origin', mirror.url])
569
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000570 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000571 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000572 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
573 return self._Capture(['rev-parse', '--verify', 'HEAD'])
574
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000575 self._maybe_break_locks(options)
576
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000577 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000578 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000579
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000580 # See if the url has changed (the unittests use git://foo for the url, let
581 # that through).
582 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
583 return_early = False
584 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
585 # unit test pass. (and update the comment above)
586 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
587 # This allows devs to use experimental repos which have a different url
588 # but whose branch(s) are the same as official repos.
Raul Tambrecd862e32019-05-10 21:19:00 +0000589 if (current_url.rstrip('/') != url.rstrip('/') and url != 'git://foo' and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000590 subprocess2.capture(
Aaron Gableac9b0f32019-04-18 17:38:37 +0000591 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000592 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000593 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000594 if not (options.force or options.reset):
595 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700596 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000597 # Switch over to the new upstream
598 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000599 if mirror:
600 with open(os.path.join(
601 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
602 'w') as fh:
603 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000604 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
605 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000606
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000607 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000608 else:
John Budorick882c91e2018-07-12 22:11:41 +0000609 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000610
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000611 if return_early:
612 return self._Capture(['rev-parse', '--verify', 'HEAD'])
613
msb@chromium.org5bde4852009-12-14 16:47:12 +0000614 cur_branch = self._GetCurrentBranch()
615
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000616 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000617 # 0) HEAD is detached. Probably from our initial clone.
618 # - make sure HEAD is contained by a named ref, then update.
619 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700620 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000621 # - try to rebase onto the new hash or branch
622 # 2) current branch is tracking a remote branch with local committed
623 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000624 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000625 # 3) current branch is tracking a remote branch w/or w/out changes, and
626 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000627 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000628 # 4) current branch is tracking a remote branch, but DEPS switches to a
629 # different remote branch, and
630 # a) current branch has no local changes, and --force:
631 # - checkout new branch
632 # b) current branch has local changes, and --force and --reset:
633 # - checkout new branch
634 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000635
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000636 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
637 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000638 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
639 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000640 if cur_branch is None:
641 upstream_branch = None
642 current_type = "detached"
643 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000644 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000645 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
646 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
647 current_type = "hash"
648 logging.debug("Current branch is not tracking an upstream (remote)"
649 " branch.")
650 elif upstream_branch.startswith('refs/remotes'):
651 current_type = "branch"
652 else:
653 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000654
Edward Lemur579c9862018-07-13 23:17:51 +0000655 self._SetFetchConfig(options)
Edward Lemur579c9862018-07-13 23:17:51 +0000656
Michael Spang73fac912019-03-08 18:44:19 +0000657 # Fetch upstream if we don't already have |revision|.
John Budorick882c91e2018-07-12 22:11:41 +0000658 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
Michael Spang73fac912019-03-08 18:44:19 +0000659 self._Fetch(options, prune=options.force)
660
661 if not scm.GIT.IsValidRevision(self.checkout_path, revision,
662 sha_only=True):
663 # Update the remotes first so we have all the refs.
664 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
665 cwd=self.checkout_path)
666 if verbose:
667 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000668
John Budorick882c91e2018-07-12 22:11:41 +0000669 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200670
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000671 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000672 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000673 target = 'HEAD'
674 if options.upstream and upstream_branch:
675 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800676 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000677
msb@chromium.org786fb682010-06-02 15:16:23 +0000678 if current_type == 'detached':
679 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800680 # We just did a Scrub, this is as clean as it's going to get. In
681 # particular if HEAD is a commit that contains two versions of the same
682 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
683 # to actually "Clean" the checkout; that commit is uncheckoutable on this
684 # system. The best we can do is carry forward to the checkout step.
685 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000686 self._CheckClean(revision)
687 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000688 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000689 self.Print('Up-to-date; skipping checkout.')
690 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000691 # 'git checkout' may need to overwrite existing untracked files. Allow
692 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000693 self._Checkout(
694 options,
John Budorick882c91e2018-07-12 22:11:41 +0000695 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000696 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000697 quiet=True,
698 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000699 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000700 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000701 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000702 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700703 # Can't find a merge-base since we don't know our upstream. That makes
704 # this command VERY likely to produce a rebase failure. For now we
705 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000706 upstream_branch = self.remote
707 if options.revision or deps_revision:
708 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700709 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700710 printed_path=printed_path, merge=options.merge)
711 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000712 elif rev_type == 'hash':
713 # case 2
714 self._AttemptRebase(upstream_branch, file_list, options,
715 newbase=revision, printed_path=printed_path,
716 merge=options.merge)
717 printed_path = True
718 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000719 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000720 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000721 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000722 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000723 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000724 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000725 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000726 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
727 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000728 force_switch = False
729 if options.force:
730 try:
John Budorick882c91e2018-07-12 22:11:41 +0000731 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000732 # case 4a
733 force_switch = True
734 except gclient_utils.Error as e:
735 if options.reset:
736 # case 4b
737 force_switch = True
738 else:
739 switch_error = '%s\n%s' % (e.message, switch_error)
740 if force_switch:
741 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000742 (upstream_branch, new_base))
743 switch_branch = 'gclient_' + remote_ref[1]
744 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000745 self._Checkout(options, switch_branch, force=True, quiet=True)
746 else:
747 # case 4c
748 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000749 else:
750 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800751 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000752 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000753 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000754 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000755 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000756 if options.merge:
757 merge_args.append('--ff')
758 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000759 merge_args.append('--ff-only')
760 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000761 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000762 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700763 rebase_files = []
Edward Lemur979fa782019-08-13 22:44:05 +0000764 if re.match(b'fatal: Not possible to fast-forward, aborting.',
765 e.stderr):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000766 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000767 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700768 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000769 printed_path = True
770 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000771 if not options.auto_rebase:
772 try:
773 action = self._AskForData(
774 'Cannot %s, attempt to rebase? '
775 '(y)es / (q)uit / (s)kip : ' %
776 ('merge' if options.merge else 'fast-forward merge'),
777 options)
778 except ValueError:
779 raise gclient_utils.Error('Invalid Character')
780 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700781 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000782 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000783 printed_path = True
784 break
785 elif re.match(r'quit|q', action, re.I):
786 raise gclient_utils.Error("Can't fast-forward, please merge or "
787 "rebase manually.\n"
788 "cd %s && git " % self.checkout_path
789 + "rebase %s" % upstream_branch)
790 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000791 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000792 return
793 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000794 self.Print('Input not recognized')
Edward Lemur979fa782019-08-13 22:44:05 +0000795 elif re.match(b"error: Your local changes to '.*' would be "
796 b"overwritten by merge. Aborting.\nPlease, commit your "
797 b"changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000798 e.stderr):
799 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000800 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700801 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000802 printed_path = True
Edward Lemur979fa782019-08-13 22:44:05 +0000803 raise gclient_utils.Error(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000804 else:
805 # Some other problem happened with the merge
806 logging.error("Error during fast-forward merge in %s!" % self.relpath)
Edward Lemur979fa782019-08-13 22:44:05 +0000807 self.Print(e.stderr.decode('utf-8'))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000808 raise
809 else:
810 # Fast-forward merge was successful
811 if not re.match('Already up-to-date.', merge_output) or verbose:
812 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700813 self.Print('_____ %s at %s' % (self.relpath, revision),
814 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000815 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000816 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000817 if not verbose:
818 # Make the output a little prettier. It's nice to have some
819 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000820 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000821
agablec3937b92016-10-25 10:13:03 -0700822 if file_list is not None:
823 file_list.extend(
824 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000825
826 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000827 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700828 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000829 '\nConflict while rebasing this branch.\n'
830 'Fix the conflict and run gclient again.\n'
831 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700832 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000833
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000834 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000835 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
836 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000837
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000838 # If --reset and --delete_unversioned_trees are specified, remove any
839 # untracked directories.
840 if options.reset and options.delete_unversioned_trees:
841 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
842 # merge-base by default), so doesn't include untracked files. So we use
843 # 'git ls-files --directory --others --exclude-standard' here directly.
844 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800845 ['-c', 'core.quotePath=false', 'ls-files',
846 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000847 self.checkout_path)
848 for path in (p for p in paths.splitlines() if p.endswith('/')):
849 full_path = os.path.join(self.checkout_path, path)
850 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000851 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000852 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000853
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000854 return self._Capture(['rev-parse', '--verify', 'HEAD'])
855
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000856 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000857 """Reverts local modifications.
858
859 All reverted files will be appended to file_list.
860 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000861 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000862 # revert won't work if the directory doesn't exist. It needs to
863 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000864 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000865 # Don't reuse the args.
866 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000867
868 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000869 if options.upstream:
870 if self._GetCurrentBranch():
871 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
872 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000873 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000874 if not deps_revision:
875 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000876 if deps_revision.startswith('refs/heads/'):
877 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700878 try:
879 deps_revision = self.GetUsableRev(deps_revision, options)
880 except NoUsableRevError as e:
881 # If the DEPS entry's url and hash changed, try to update the origin.
882 # See also http://crbug.com/520067.
883 logging.warn(
884 'Couldn\'t find usable revision, will retrying to update instead: %s',
885 e.message)
886 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000887
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000888 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800889 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000890
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800891 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000892 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000893
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000894 if file_list is not None:
895 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
896
897 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000898 """Returns revision"""
899 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000900
msb@chromium.orge28e4982009-09-25 20:51:45 +0000901 def runhooks(self, options, args, file_list):
902 self.status(options, args, file_list)
903
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000904 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000905 """Display status information."""
906 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000907 self.Print('________ couldn\'t run status in %s:\n'
908 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000909 else:
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700910 try:
911 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
912 except subprocess2.CalledProcessError:
913 merge_base = []
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 Lemurbdbe07f2019-03-28 22:14:45 +0000949 def _GetMirror(self, url, options, 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,
John Budorick882c91e2018-07-12 22:11:41 +0000955 'refs': []
hinoka@google.comb1b54572014-04-16 22:29:23 +0000956 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000957 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
958 mirror_kwargs['refs'].append('refs/branch-heads/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000959 elif revision_ref and revision_ref.startswith('refs/branch-heads/'):
960 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000961 if hasattr(options, 'with_tags') and options.with_tags:
962 mirror_kwargs['refs'].append('refs/tags/*')
Edward Lemurbdbe07f2019-03-28 22:14:45 +0000963 elif revision_ref and revision_ref.startswith('refs/tags/'):
964 mirror_kwargs['refs'].append(revision_ref)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000965 return git_cache.Mirror(url, **mirror_kwargs)
966
John Budorick882c91e2018-07-12 22:11:41 +0000967 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800968 """Update a git mirror by fetching the latest commits from the remote,
969 unless mirror already contains revision whose type is sha1 hash.
970 """
John Budorick882c91e2018-07-12 22:11:41 +0000971 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800972 if options.verbose:
973 self.Print('skipping mirror update, it has rev=%s already' % revision,
974 timestamp=False)
975 return
976
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000977 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000978 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000979 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000980 depth = 10
981 else:
982 depth = 10000
983 else:
984 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000985 mirror.populate(verbose=options.verbose,
986 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000987 depth=depth,
988 ignore_lock=getattr(options, 'ignore_locks', False),
989 lock_timeout=getattr(options, 'lock_timeout', 0))
990 mirror.unlock()
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000991
John Budorick882c91e2018-07-12 22:11:41 +0000992 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000993 """Clone a git repository from the given URL.
994
msb@chromium.org786fb682010-06-02 15:16:23 +0000995 Once we've cloned the repo, we checkout a working branch if the specified
996 revision is a branch head. If it is a tag or a specific commit, then we
997 leave HEAD detached as it makes future updates simpler -- in this case the
998 user should first create a new branch or switch to an existing branch before
999 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001000 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001001 # git clone doesn't seem to insert a newline properly before printing
1002 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001003 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +00001004 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +00001005 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001006 if self.cache_dir:
1007 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001008 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001009 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001010 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +00001011 # If the parent directory does not exist, Git clone on Windows will not
1012 # create it, so we need to do it manually.
1013 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001014 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001015
1016 template_dir = None
1017 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +00001018 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001019 # In the case of a subproject, the pinned sha is not necessarily the
1020 # head of the remote branch (so we can't just use --depth=N). Instead,
1021 # we tell git to fetch all the remote objects from SHA..HEAD by means of
1022 # a template git dir which has a 'shallow' file pointing to the sha.
1023 template_dir = tempfile.mkdtemp(
1024 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
1025 dir=parent_dir)
1026 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
1027 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
1028 template_file.write(revision)
1029 clone_cmd.append('--template=' + template_dir)
1030 else:
1031 # Otherwise, we're just interested in the HEAD. Just use --depth.
1032 clone_cmd.append('--depth=1')
1033
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001034 tmp_dir = tempfile.mkdtemp(
1035 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
1036 dir=parent_dir)
1037 try:
1038 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001039 if self.print_outbuf:
1040 print_stdout = True
Edward Lemur24146be2019-08-01 21:44:52 +00001041 filter_fn = None
Edward Lemur231f5ea2018-01-31 19:02:36 +01001042 else:
1043 print_stdout = False
Edward Lemur24146be2019-08-01 21:44:52 +00001044 filter_fn = self.filter
Edward Lemur231f5ea2018-01-31 19:02:36 +01001045 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
Edward Lemur24146be2019-08-01 21:44:52 +00001046 print_stdout=print_stdout, filter_fn=filter_fn)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001047 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +00001048 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
1049 os.path.join(self.checkout_path, '.git'))
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001050 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001051 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +00001052 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001053 finally:
1054 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001055 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +00001056 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001057 if template_dir:
1058 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +00001059 self._SetFetchConfig(options)
1060 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001061 revision = self._AutoFetchRef(options, revision)
1062 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1063 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +00001064 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +00001065 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001066 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +00001067 ('Checked out %s to a detached HEAD. Before making any commits\n'
1068 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1069 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +00001070 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001071
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001072 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001073 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001074 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001075 raise gclient_utils.Error("Background task requires input. Rerun "
1076 "gclient with --jobs=1 so that\n"
1077 "interaction is possible.")
1078 try:
1079 return raw_input(prompt)
1080 except KeyboardInterrupt:
1081 # Hide the exception.
1082 sys.exit(1)
1083
1084
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001085 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001086 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001087 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001088 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001089 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001090 revision = upstream
1091 if newbase:
1092 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001093 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001094 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001095 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001096 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001097 printed_path = True
1098 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001099 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001100
1101 if merge:
1102 merge_output = self._Capture(['merge', revision])
1103 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001104 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001105 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001106
1107 # Build the rebase command here using the args
1108 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1109 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001110 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001111 rebase_cmd.append('--verbose')
1112 if newbase:
1113 rebase_cmd.extend(['--onto', newbase])
1114 rebase_cmd.append(upstream)
1115 if branch:
1116 rebase_cmd.append(branch)
1117
1118 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001119 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
Raul Tambreb946b232019-03-26 14:48:46 +00001120 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001121 if (re.match(br'cannot rebase: you have unstaged changes', e.stderr) or
1122 re.match(br'cannot rebase: your index contains uncommitted changes',
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001123 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001124 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001125 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001126 'Cannot rebase because of unstaged changes.\n'
1127 '\'git reset --hard HEAD\' ?\n'
1128 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001129 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001130 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001131 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001132 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001133 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001134 break
1135 elif re.match(r'quit|q', rebase_action, re.I):
1136 raise gclient_utils.Error("Please merge or rebase manually\n"
1137 "cd %s && git " % self.checkout_path
1138 + "%s" % ' '.join(rebase_cmd))
1139 elif re.match(r'show|s', rebase_action, re.I):
Edward Lemur979fa782019-08-13 22:44:05 +00001140 self.Print('%s' % e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001141 continue
1142 else:
1143 gclient_utils.Error("Input not recognized")
1144 continue
Edward Lemur979fa782019-08-13 22:44:05 +00001145 elif re.search(br'^CONFLICT', e.stdout, re.M):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001146 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1147 "Fix the conflict and run gclient again.\n"
1148 "See 'man git-rebase' for details.\n")
1149 else:
Edward Lemur979fa782019-08-13 22:44:05 +00001150 self.Print(e.stdout.decode('utf-8').strip())
1151 self.Print('Rebase produced error output:\n%s' %
1152 e.stderr.decode('utf-8').strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001153 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1154 "manually.\ncd %s && git " %
1155 self.checkout_path
1156 + "%s" % ' '.join(rebase_cmd))
1157
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001158 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001159 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001160 # Make the output a little prettier. It's nice to have some
1161 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001162 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001163
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001164 @staticmethod
1165 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001166 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1167 if not ok:
1168 raise gclient_utils.Error('git version %s < minimum required %s' %
1169 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001170
John Budorick882c91e2018-07-12 22:11:41 +00001171 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001172 # Special case handling if all 3 conditions are met:
1173 # * the mirros have recently changed, but deps destination remains same,
1174 # * the git histories of mirrors are conflicting.
1175 # * git cache is used
1176 # This manifests itself in current checkout having invalid HEAD commit on
1177 # most git operations. Since git cache is used, just deleted the .git
1178 # folder, and re-create it by cloning.
1179 try:
1180 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1181 except subprocess2.CalledProcessError as e:
Edward Lemur979fa782019-08-13 22:44:05 +00001182 if (b'fatal: bad object HEAD' in e.stderr
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001183 and self.cache_dir and self.cache_dir in url):
1184 self.Print((
1185 'Likely due to DEPS change with git cache_dir, '
1186 'the current commit points to no longer existing object.\n'
1187 '%s' % e)
1188 )
1189 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001190 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001191 else:
1192 raise
1193
msb@chromium.org786fb682010-06-02 15:16:23 +00001194 def _IsRebasing(self):
1195 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1196 # have a plumbing command to determine whether a rebase is in progress, so
1197 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1198 g = os.path.join(self.checkout_path, '.git')
1199 return (
1200 os.path.isdir(os.path.join(g, "rebase-merge")) or
1201 os.path.isdir(os.path.join(g, "rebase-apply")))
1202
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001203 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001204 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1205 if os.path.exists(lockfile):
1206 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001207 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001208 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1209 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001210 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001211
msb@chromium.org786fb682010-06-02 15:16:23 +00001212 # Make sure the tree is clean; see git-rebase.sh for reference
1213 try:
1214 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001215 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001216 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001217 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001218 '\tYou have unstaged changes.\n'
1219 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001220 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001221 try:
1222 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001223 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001224 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001225 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001226 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001227 '\tYour index contains uncommitted changes\n'
1228 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001229 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001230
agable83faed02016-10-24 14:37:10 -07001231 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001232 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1233 # reference by a commit). If not, error out -- most likely a rebase is
1234 # in progress, try to detect so we can give a better error.
1235 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001236 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1237 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001238 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001239 # Commit is not contained by any rev. See if the user is rebasing:
1240 if self._IsRebasing():
1241 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001242 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001243 '\tAlready in a conflict, i.e. (no branch).\n'
1244 '\tFix the conflict and run gclient again.\n'
1245 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1246 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001247 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001248 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001249 name = ('saved-by-gclient-' +
1250 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001251 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001252 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001253 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001254
msb@chromium.org5bde4852009-12-14 16:47:12 +00001255 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001256 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001257 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001258 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001259 return None
1260 return branch
1261
borenet@google.comc3e09d22014-04-10 13:58:18 +00001262 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001263 kwargs.setdefault('cwd', self.checkout_path)
1264 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001265 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001266 env = scm.GIT.ApplyEnvVars(kwargs)
Raul Tambrecd862e32019-05-10 21:19:00 +00001267 ret = subprocess2.check_output(
1268 ['git'] + args, env=env, **kwargs).decode('utf-8')
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001269 if strip:
1270 ret = ret.strip()
1271 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001272
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001273 def _Checkout(self, options, ref, force=False, quiet=None):
1274 """Performs a 'git-checkout' operation.
1275
1276 Args:
1277 options: The configured option set
1278 ref: (str) The branch/commit to checkout
1279 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1280 'None', the behavior is inferred from 'options.verbose'.
1281 Returns: (str) The output of the checkout operation
1282 """
1283 if quiet is None:
1284 quiet = (not options.verbose)
1285 checkout_args = ['checkout']
1286 if force:
1287 checkout_args.append('--force')
1288 if quiet:
1289 checkout_args.append('--quiet')
1290 checkout_args.append(ref)
1291 return self._Capture(checkout_args)
1292
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001293 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1294 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001295 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemur9a5e3bd2019-04-02 23:37:45 +00001296 # When updating, the ref is modified to be a remote ref .
1297 # (e.g. refs/heads/NAME becomes refs/remotes/REMOTE/NAME).
1298 # Try to reverse that mapping.
1299 original_ref = scm.GIT.RemoteRefToRef(refspec, self.remote)
1300 if original_ref:
1301 refspec = original_ref + ':' + refspec
1302 # When a mirror is configured, it only fetches
1303 # refs/{heads,branch-heads,tags}/*.
1304 # If asked to fetch other refs, we must fetch those directly from the
1305 # repository, and not from the mirror.
1306 if not original_ref.startswith(
1307 ('refs/heads/', 'refs/branch-heads/', 'refs/tags/')):
1308 remote, _ = gclient_utils.SplitUrlRevision(self.url)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001309 fetch_cmd = cfg + [
1310 'fetch',
1311 remote or self.remote,
1312 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001313 if refspec:
1314 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001315
1316 if prune:
1317 fetch_cmd.append('--prune')
1318 if options.verbose:
1319 fetch_cmd.append('--verbose')
1320 elif quiet:
1321 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001322 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001323
1324 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1325 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1326
Edward Lemur579c9862018-07-13 23:17:51 +00001327 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001328 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1329 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001330 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001331 try:
1332 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1333 options)
1334 self._Run(['config', 'remote.%s.fetch' % self.remote,
1335 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1336 except subprocess2.CalledProcessError as e:
1337 # If exit code was 5, it means we attempted to unset a config that
1338 # didn't exist. Ignore it.
1339 if e.returncode != 5:
1340 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001341 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001342 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001343 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1344 '^\\+refs/branch-heads/\\*:.*$']
1345 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001346 if hasattr(options, 'with_tags') and options.with_tags:
1347 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1348 '+refs/tags/*:refs/tags/*',
1349 '^\\+refs/tags/\\*:.*$']
1350 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001351
John Budorick882c91e2018-07-12 22:11:41 +00001352 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001353 """Attempts to fetch |revision| if not available in local repo.
1354
1355 Returns possibly updated revision."""
John Budorick882c91e2018-07-12 22:11:41 +00001356 try:
1357 self._Capture(['rev-parse', revision])
1358 except subprocess2.CalledProcessError:
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001359 self._Fetch(options, refspec=revision)
1360 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1361 return revision
1362
Edward Lemur24146be2019-08-01 21:44:52 +00001363 def _Run(self, args, options, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001364 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001365 kwargs.setdefault('cwd', self.checkout_path)
Edward Lemur24146be2019-08-01 21:44:52 +00001366 kwargs.setdefault('filter_fn', self.filter)
1367 kwargs.setdefault('show_header', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001368 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001369 cmd = ['git'] + args
Edward Lemur24146be2019-08-01 21:44:52 +00001370 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001371
1372
1373class CipdPackage(object):
1374 """A representation of a single CIPD package."""
1375
John Budorickd3ba72b2018-03-20 12:27:42 -07001376 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001377 self._authority_for_subdir = authority_for_subdir
1378 self._name = name
1379 self._version = version
1380
1381 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001382 def authority_for_subdir(self):
1383 """Whether this package has authority to act on behalf of its subdir.
1384
1385 Some operations should only be performed once per subdirectory. A package
1386 that has authority for its subdirectory is the only package that should
1387 perform such operations.
1388
1389 Returns:
1390 bool; whether this package has subdir authority.
1391 """
1392 return self._authority_for_subdir
1393
1394 @property
1395 def name(self):
1396 return self._name
1397
1398 @property
1399 def version(self):
1400 return self._version
1401
1402
1403class CipdRoot(object):
1404 """A representation of a single CIPD root."""
1405 def __init__(self, root_dir, service_url):
1406 self._all_packages = set()
1407 self._mutator_lock = threading.Lock()
1408 self._packages_by_subdir = collections.defaultdict(list)
1409 self._root_dir = root_dir
1410 self._service_url = service_url
1411
1412 def add_package(self, subdir, package, version):
1413 """Adds a package to this CIPD root.
1414
1415 As far as clients are concerned, this grants both root and subdir authority
1416 to packages arbitrarily. (The implementation grants root authority to the
1417 first package added and subdir authority to the first package added for that
1418 subdir, but clients should not depend on or expect that behavior.)
1419
1420 Args:
1421 subdir: str; relative path to where the package should be installed from
1422 the cipd root directory.
1423 package: str; the cipd package name.
1424 version: str; the cipd package version.
1425 Returns:
1426 CipdPackage; the package that was created and added to this root.
1427 """
1428 with self._mutator_lock:
1429 cipd_package = CipdPackage(
1430 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001431 not self._packages_by_subdir[subdir])
1432 self._all_packages.add(cipd_package)
1433 self._packages_by_subdir[subdir].append(cipd_package)
1434 return cipd_package
1435
1436 def packages(self, subdir):
1437 """Get the list of configured packages for the given subdir."""
1438 return list(self._packages_by_subdir[subdir])
1439
1440 def clobber(self):
1441 """Remove the .cipd directory.
1442
1443 This is useful for forcing ensure to redownload and reinitialize all
1444 packages.
1445 """
1446 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001447 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001448 try:
1449 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1450 except OSError:
1451 if os.path.exists(cipd_cache_dir):
1452 raise
1453
1454 @contextlib.contextmanager
1455 def _create_ensure_file(self):
1456 try:
1457 ensure_file = None
1458 with tempfile.NamedTemporaryFile(
Raul Tambreb946b232019-03-26 14:48:46 +00001459 suffix='.ensure', delete=False, mode='w') as ensure_file:
John Budorick302bb842018-07-17 23:49:17 +00001460 ensure_file.write('$ParanoidMode CheckPresence\n\n')
Raul Tambreb946b232019-03-26 14:48:46 +00001461 for subdir, packages in sorted(self._packages_by_subdir.items()):
John Budorick0f7b2002018-01-19 15:46:17 -08001462 ensure_file.write('@Subdir %s\n' % subdir)
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001463 for package in sorted(packages, key=lambda p: p.name):
John Budorick0f7b2002018-01-19 15:46:17 -08001464 ensure_file.write('%s %s\n' % (package.name, package.version))
1465 ensure_file.write('\n')
1466 yield ensure_file.name
1467 finally:
1468 if ensure_file is not None and os.path.exists(ensure_file.name):
1469 os.remove(ensure_file.name)
1470
1471 def ensure(self):
1472 """Run `cipd ensure`."""
1473 with self._mutator_lock:
1474 with self._create_ensure_file() as ensure_file:
1475 cmd = [
1476 'cipd', 'ensure',
1477 '-log-level', 'error',
1478 '-root', self.root_dir,
1479 '-ensure-file', ensure_file,
1480 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001481 gclient_utils.CheckCallAndFilter(
1482 cmd, print_stdout=True, show_header=True)
John Budorick0f7b2002018-01-19 15:46:17 -08001483
John Budorickd3ba72b2018-03-20 12:27:42 -07001484 def run(self, command):
1485 if command == 'update':
1486 self.ensure()
1487 elif command == 'revert':
1488 self.clobber()
1489 self.ensure()
1490
John Budorick0f7b2002018-01-19 15:46:17 -08001491 def created_package(self, package):
1492 """Checks whether this root created the given package.
1493
1494 Args:
1495 package: CipdPackage; the package to check.
1496 Returns:
1497 bool; whether this root created the given package.
1498 """
1499 return package in self._all_packages
1500
1501 @property
1502 def root_dir(self):
1503 return self._root_dir
1504
1505 @property
1506 def service_url(self):
1507 return self._service_url
1508
1509
1510class CipdWrapper(SCMWrapper):
1511 """Wrapper for CIPD.
1512
1513 Currently only supports chrome-infra-packages.appspot.com.
1514 """
John Budorick3929e9e2018-02-04 18:18:07 -08001515 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001516
1517 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1518 out_cb=None, root=None, package=None):
1519 super(CipdWrapper, self).__init__(
1520 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1521 out_cb=out_cb)
1522 assert root.created_package(package)
1523 self._package = package
1524 self._root = root
1525
1526 #override
1527 def GetCacheMirror(self):
1528 return None
1529
1530 #override
1531 def GetActualRemoteURL(self, options):
1532 return self._root.service_url
1533
1534 #override
1535 def DoesRemoteURLMatch(self, options):
1536 del options
1537 return True
1538
1539 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001540 """Does nothing.
1541
1542 CIPD packages should be reverted at the root by running
1543 `CipdRoot.run('revert')`.
1544 """
1545 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001546
1547 def diff(self, options, args, file_list):
1548 """CIPD has no notion of diffing."""
1549 pass
1550
1551 def pack(self, options, args, file_list):
1552 """CIPD has no notion of diffing."""
1553 pass
1554
1555 def revinfo(self, options, args, file_list):
1556 """Grab the instance ID."""
1557 try:
1558 tmpdir = tempfile.mkdtemp()
1559 describe_json_path = os.path.join(tmpdir, 'describe.json')
1560 cmd = [
1561 'cipd', 'describe',
1562 self._package.name,
1563 '-log-level', 'error',
1564 '-version', self._package.version,
1565 '-json-output', describe_json_path
1566 ]
Edward Lemur24146be2019-08-01 21:44:52 +00001567 gclient_utils.CheckCallAndFilter(cmd)
John Budorick0f7b2002018-01-19 15:46:17 -08001568 with open(describe_json_path) as f:
1569 describe_json = json.load(f)
1570 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1571 finally:
1572 gclient_utils.rmtree(tmpdir)
1573
1574 def status(self, options, args, file_list):
1575 pass
1576
1577 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001578 """Does nothing.
1579
1580 CIPD packages should be updated at the root by running
1581 `CipdRoot.run('update')`.
1582 """
1583 pass