blob: 69faa6cbc2bfd9048cf964fd9fb98fb07dc44ca7 [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
hinoka@google.com2f2ca142014-01-07 03:59:18 +000021import urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000022
hinoka@google.com2f2ca142014-01-07 03:59:18 +000023import download_from_google_storage
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000024import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +000025import git_cache
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000026import scm
borenet@google.comb2256212014-05-07 20:57:28 +000027import shutil
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000028import subprocess2
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000029
30
szager@chromium.org71cbb502013-04-19 23:30:15 +000031THIS_FILE_PATH = os.path.abspath(__file__)
32
hinoka@google.com2f2ca142014-01-07 03:59:18 +000033GSUTIL_DEFAULT_PATH = os.path.join(
hinoka@chromium.orgb091aa52014-12-20 01:47:31 +000034 os.path.dirname(os.path.abspath(__file__)), 'gsutil.py')
hinoka@google.com2f2ca142014-01-07 03:59:18 +000035
maruel@chromium.org79d62372015-06-01 18:50:55 +000036
smutae7ea312016-07-18 11:59:41 -070037class NoUsableRevError(gclient_utils.Error):
38 """Raised if requested revision isn't found in checkout."""
39
40
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000041class DiffFiltererWrapper(object):
42 """Simple base class which tracks which file is being diffed and
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000043 replaces instances of its file name in the original and
agable41e3a6c2016-10-20 11:36:56 -070044 working copy lines of the git diff output."""
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000045 index_string = None
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000046 original_prefix = "--- "
47 working_prefix = "+++ "
48
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000049 def __init__(self, relpath, print_func):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000050 # Note that we always use '/' as the path separator to be
agable41e3a6c2016-10-20 11:36:56 -070051 # consistent with cygwin-style output on Windows
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000052 self._relpath = relpath.replace("\\", "/")
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000053 self._current_file = None
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000054 self._print_func = print_func
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000055
maruel@chromium.org6e29d572010-06-04 17:32:20 +000056 def SetCurrentFile(self, current_file):
57 self._current_file = current_file
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000058
iannucci@chromium.org3830a672013-02-19 20:15:14 +000059 @property
60 def _replacement_file(self):
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000061 return posixpath.join(self._relpath, self._current_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000062
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000063 def _Replace(self, line):
64 return line.replace(self._current_file, self._replacement_file)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000065
66 def Filter(self, line):
67 if (line.startswith(self.index_string)):
68 self.SetCurrentFile(line[len(self.index_string):])
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000069 line = self._Replace(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000070 else:
71 if (line.startswith(self.original_prefix) or
72 line.startswith(self.working_prefix)):
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +000073 line = self._Replace(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000074 self._print_func(line)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +000075
76
haitao.feng@intel.com306080c2012-05-04 13:11:29 +000077class GitDiffFilterer(DiffFiltererWrapper):
78 index_string = "diff --git "
79
80 def SetCurrentFile(self, current_file):
81 # Get filename by parsing "a/<filename> b/<filename>"
82 self._current_file = current_file[:(len(current_file)/2)][2:]
83
84 def _Replace(self, line):
85 return re.sub("[a|b]/" + self._current_file, self._replacement_file, line)
86
87
msb@chromium.orgcb5442b2009-09-22 16:51:24 +000088# SCMWrapper base class
89
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000090class SCMWrapper(object):
91 """Add necessary glue between all the supported SCM.
92
msb@chromium.orgd6504212010-01-13 17:34:31 +000093 This is the abstraction layer to bind to different SCM.
94 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000095 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
Edward Lemur231f5ea2018-01-31 19:02:36 +010096 out_cb=None, print_outbuf=False):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000097 self.url = url
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +000098 self._root_dir = root_dir
99 if self._root_dir:
100 self._root_dir = self._root_dir.replace('/', os.sep)
101 self.relpath = relpath
102 if self.relpath:
103 self.relpath = self.relpath.replace('/', os.sep)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000104 if self.relpath and self._root_dir:
105 self.checkout_path = os.path.join(self._root_dir, self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000106 if out_fh is None:
107 out_fh = sys.stdout
108 self.out_fh = out_fh
109 self.out_cb = out_cb
Edward Lemur231f5ea2018-01-31 19:02:36 +0100110 self.print_outbuf = print_outbuf
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000111
112 def Print(self, *args, **kwargs):
113 kwargs.setdefault('file', self.out_fh)
114 if kwargs.pop('timestamp', True):
115 self.out_fh.write('[%s] ' % gclient_utils.Elapsed())
116 print(*args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000117
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000118 def RunCommand(self, command, options, args, file_list=None):
agabledebf6c82016-12-21 12:50:12 -0800119 commands = ['update', 'updatesingle', 'revert',
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000120 'revinfo', 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000121
122 if not command in commands:
123 raise gclient_utils.Error('Unknown command %s' % command)
124
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000125 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000126 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000127 command, self.__class__.__name__))
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000128
129 return getattr(self, command)(options, args, file_list)
130
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000131 @staticmethod
132 def _get_first_remote_url(checkout_path):
133 log = scm.GIT.Capture(
134 ['config', '--local', '--get-regexp', r'remote.*.url'],
135 cwd=checkout_path)
136 # Get the second token of the first line of the log.
137 return log.splitlines()[0].split(' ', 1)[1]
138
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000139 def GetCacheMirror(self):
Robert Iannuccia19649b2018-06-29 16:31:45 +0000140 if getattr(self, 'cache_dir', None):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000141 url, _ = gclient_utils.SplitUrlRevision(self.url)
142 return git_cache.Mirror(url)
143 return None
144
smut@google.comd33eab32014-07-07 19:35:18 +0000145 def GetActualRemoteURL(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000146 """Attempt to determine the remote URL for this SCMWrapper."""
smut@google.comd33eab32014-07-07 19:35:18 +0000147 # Git
borenet@google.combda475e2014-03-24 19:04:45 +0000148 if os.path.exists(os.path.join(self.checkout_path, '.git')):
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000149 actual_remote_url = self._get_first_remote_url(self.checkout_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000150
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000151 mirror = self.GetCacheMirror()
152 # If the cache is used, obtain the actual remote URL from there.
153 if (mirror and mirror.exists() and
154 mirror.mirror_path.replace('\\', '/') ==
155 actual_remote_url.replace('\\', '/')):
156 actual_remote_url = self._get_first_remote_url(mirror.mirror_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000157 return actual_remote_url
borenet@google.com88d10082014-03-21 17:24:48 +0000158 return None
159
borenet@google.com4e9be262014-04-08 19:40:30 +0000160 def DoesRemoteURLMatch(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000161 """Determine whether the remote URL of this checkout is the expected URL."""
162 if not os.path.exists(self.checkout_path):
163 # A checkout which doesn't exist can't be broken.
164 return True
165
smut@google.comd33eab32014-07-07 19:35:18 +0000166 actual_remote_url = self.GetActualRemoteURL(options)
borenet@google.com88d10082014-03-21 17:24:48 +0000167 if actual_remote_url:
borenet@google.com8156c9f2014-04-01 16:41:36 +0000168 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
169 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
borenet@google.com88d10082014-03-21 17:24:48 +0000170 else:
171 # This may occur if the self.checkout_path exists but does not contain a
agable41e3a6c2016-10-20 11:36:56 -0700172 # valid git checkout.
borenet@google.com88d10082014-03-21 17:24:48 +0000173 return False
174
borenet@google.comb09097a2014-04-09 19:09:08 +0000175 def _DeleteOrMove(self, force):
176 """Delete the checkout directory or move it out of the way.
177
178 Args:
179 force: bool; if True, delete the directory. Otherwise, just move it.
180 """
borenet@google.comb2256212014-05-07 20:57:28 +0000181 if force and os.environ.get('CHROME_HEADLESS') == '1':
182 self.Print('_____ Conflicting directory found in %s. Removing.'
183 % self.checkout_path)
184 gclient_utils.AddWarning('Conflicting directory %s deleted.'
185 % self.checkout_path)
186 gclient_utils.rmtree(self.checkout_path)
187 else:
188 bad_scm_dir = os.path.join(self._root_dir, '_bad_scm',
189 os.path.dirname(self.relpath))
190
191 try:
192 os.makedirs(bad_scm_dir)
193 except OSError as e:
194 if e.errno != errno.EEXIST:
195 raise
196
197 dest_path = tempfile.mkdtemp(
198 prefix=os.path.basename(self.relpath),
199 dir=bad_scm_dir)
200 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
201 % (self.checkout_path, dest_path))
202 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
203 % (self.checkout_path, dest_path))
204 shutil.move(self.checkout_path, dest_path)
borenet@google.comb09097a2014-04-09 19:09:08 +0000205
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000206
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000207class GitWrapper(SCMWrapper):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000208 """Wrapper for Git"""
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000209 name = 'git'
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000210 remote = 'origin'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000211
Robert Iannuccia19649b2018-06-29 16:31:45 +0000212 @property
213 def cache_dir(self):
214 try:
215 return git_cache.Mirror.GetCachePath()
216 except RuntimeError:
217 return None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000218
John Budorick0f7b2002018-01-19 15:46:17 -0800219 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000220 """Removes 'git+' fake prefix from git URL."""
221 if url.startswith('git+http://') or url.startswith('git+https://'):
222 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800223 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000224 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
225 if self.out_cb:
226 filter_kwargs['predicate'] = self.out_cb
227 self.filter = gclient_utils.GitFilter(**filter_kwargs)
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000228
mukai@chromium.org9e3e82c2012-04-18 12:55:43 +0000229 @staticmethod
230 def BinaryExists():
231 """Returns true if the command exists."""
232 try:
233 # We assume git is newer than 1.7. See: crbug.com/114483
234 result, version = scm.GIT.AssertVersion('1.7')
235 if not result:
236 raise gclient_utils.Error('Git version is older than 1.7: %s' % version)
237 return result
238 except OSError:
239 return False
240
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000241 def GetCheckoutRoot(self):
242 return scm.GIT.GetCheckoutRoot(self.checkout_path)
243
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000244 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000245 """Returns the given revision's date in ISO-8601 format (which contains the
246 time zone)."""
247 # TODO(floitsch): get the time-stamp of the given revision and not just the
248 # time-stamp of the currently checked out revision.
249 return self._Capture(['log', '-n', '1', '--format=%ai'])
250
Aaron Gablef4068aa2017-12-12 15:14:09 -0800251 def _GetDiffFilenames(self, base):
252 """Returns the names of files modified since base."""
253 return self._Capture(
254 # Filter to remove base if it is None.
255 filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only', base])
256 ).split()
257
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000258 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 15:45:56 -0800259 _, revision = gclient_utils.SplitUrlRevision(self.url)
260 if not revision:
261 revision = 'refs/remotes/%s/master' % self.remote
262 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000263
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000264 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000265 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000266 repository.
267
268 The patch file is generated from a diff of the merge base of HEAD and
269 its upstream branch.
270 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700271 try:
272 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
273 except subprocess2.CalledProcessError:
274 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000275 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700276 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000277 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000278 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000279
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800280 def _Scrub(self, target, options):
281 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000282 quiet = []
283 if not options.verbose:
284 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800285 self._Run(['reset', '--hard', target] + quiet, options)
286 if options.force and options.delete_unversioned_trees:
287 # where `target` is a commit that contains both upper and lower case
288 # versions of the same file on a case insensitive filesystem, we are
289 # actually in a broken state here. The index will have both 'a' and 'A',
290 # but only one of them will exist on the disk. To progress, we delete
291 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800292 output = self._Capture([
293 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800294 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800295 # --porcelain (v1) looks like:
296 # XY filename
297 try:
298 filename = line[3:]
299 self.Print('_____ Deleting residual after reset: %r.' % filename)
300 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800301 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800302 except OSError:
303 pass
304
John Budorick882c91e2018-07-12 22:11:41 +0000305 def _FetchAndReset(self, revision, file_list, options):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800306 """Equivalent to git fetch; git reset."""
Edward Lemur579c9862018-07-13 23:17:51 +0000307 self._SetFetchConfig(options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000308
dnj@chromium.org680f2172014-06-25 00:39:32 +0000309 self._Fetch(options, prune=True, quiet=options.verbose)
John Budorick882c91e2018-07-12 22:11:41 +0000310 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000311 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800312 files = self._Capture(
313 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000314 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
315
szager@chromium.org8a139702014-06-20 15:55:01 +0000316 def _DisableHooks(self):
317 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
318 if not os.path.isdir(hook_dir):
319 return
320 for f in os.listdir(hook_dir):
321 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000322 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
323 if os.path.exists(disabled_hook_path):
324 os.remove(disabled_hook_path)
325 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000326
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000327 def _maybe_break_locks(self, options):
328 """This removes all .lock files from this repo's .git directory, if the
329 user passed the --break_repo_locks command line flag.
330
331 In particular, this will cleanup index.lock files, as well as ref lock
332 files.
333 """
334 if options.break_repo_locks:
335 git_dir = os.path.join(self.checkout_path, '.git')
336 for path, _, filenames in os.walk(git_dir):
337 for filename in filenames:
338 if filename.endswith('.lock'):
339 to_break = os.path.join(path, filename)
340 self.Print('breaking lock: %s' % (to_break,))
341 try:
342 os.remove(to_break)
343 except OSError as ex:
344 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
345 raise
346
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000347 # TODO(ehmaldonado): Remove after bot_update is modified to pass the patch's
348 # branch.
349 def _GetTargetBranchForCommit(self, commit):
Edward Lemurca7d8812018-07-24 17:42:45 +0000350 """Get the remote branch a commit is part of."""
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000351 _WELL_KNOWN_BRANCHES = [
352 'refs/remotes/origin/master',
353 'refs/remotes/origin/infra/config',
354 'refs/remotes/origin/lkgr',
355 ]
356 for branch in _WELL_KNOWN_BRANCHES:
357 if scm.GIT.IsAncestor(self.checkout_path, commit, branch):
358 return branch
Edward Lemurca7d8812018-07-24 17:42:45 +0000359 remote_refs = self._Capture(
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000360 ['for-each-ref', 'refs/remotes/%s' % self.remote,
Edward Lemurca7d8812018-07-24 17:42:45 +0000361 '--format=%(refname)']).splitlines()
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000362 for ref in sorted(remote_refs, reverse=True):
Edward Lemurca7d8812018-07-24 17:42:45 +0000363 if scm.GIT.IsAncestor(self.checkout_path, commit, ref):
364 return ref
365 self.Print('Failed to find a remote ref that contains %s. '
366 'Candidate refs were %s.' % (commit, remote_refs))
367 # Fallback to the commit we got.
368 # This means that apply_path_ref will try to find the merge-base between the
369 # patch and the commit (which is most likely the commit) and cherry-pick
370 # everything in between.
371 return commit
372
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000373 def apply_patch_ref(self, patch_repo, patch_ref, target_branch, options,
374 file_list):
375 """Apply a patch on top of the revision we're synced at.
376
377 The patch ref is given by |patch_repo|@|patch_ref|, and the current revision
378 is |base_rev|.
379 We also need the |target_branch| that the patch was uploaded against. We use
380 it to find a merge base between |patch_rev| and |base_rev|, so we can find
381 what commits constitute the patch:
382
383 Graphically, it looks like this:
384
385 ... -> merge_base -> [possibly already landed commits] -> target_branch
386 \
387 -> [possibly not yet landed dependent CLs] -> patch_rev
388
389 Next, we apply the commits |merge_base..patch_rev| on top of whatever is
390 currently checked out, denoted |base_rev|. Typically, it'd be a revision
391 from |target_branch|, but this is not required.
392
393 Graphically, we cherry pick |merge_base..patch_rev| on top of |base_rev|:
394
395 ... -> base_rev -> [possibly not yet landed dependent CLs] -> patch_rev
396
397 After application, if |options.reset_patch_ref| is specified, we soft reset
398 the just cherry-picked changes, keeping them in git index only.
399
400 Args:
401 patch_repo: The patch origin. e.g. 'https://foo.googlesource.com/bar'
402 patch_ref: The ref to the patch. e.g. 'refs/changes/1234/34/1'.
403 target_branch: The branch the patch was uploaded against.
404 e.g. 'refs/heads/master' or 'refs/heads/infra/config'.
405 options: The options passed to gclient.
406 file_list: A list where modified files will be appended.
407 """
408
Edward Lemurca7d8812018-07-24 17:42:45 +0000409 # Abort any cherry-picks in progress.
410 try:
411 self._Capture(['cherry-pick', '--abort'])
412 except subprocess2.CalledProcessError:
413 pass
414
Edward Lesmesc621b212018-03-21 20:26:56 -0400415 base_rev = self._Capture(['rev-parse', 'HEAD'])
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000416 target_branch = target_branch or self._GetTargetBranchForCommit(base_rev)
Edward Lesmesc621b212018-03-21 20:26:56 -0400417 self.Print('===Applying patch ref===')
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000418 self.Print('Patch ref is %r @ %r. Target branch for patch is %r. '
419 'Current HEAD is %r. Current dir is %r' % (
420 patch_repo, patch_ref, target_branch, base_rev,
421 self.checkout_path))
Edward Lesmesc621b212018-03-21 20:26:56 -0400422 self._Capture(['reset', '--hard'])
423 self._Capture(['fetch', patch_repo, patch_ref])
Edward Lemurca7d8812018-07-24 17:42:45 +0000424 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400425
Edward Lemurca7d8812018-07-24 17:42:45 +0000426 try:
427 if not options.rebase_patch_ref:
428 self._Capture(['checkout', patch_rev])
429 else:
430 # Find the merge-base between the branch_rev and patch_rev to find out
431 # the changes we need to cherry-pick on top of base_rev.
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000432 merge_base = self._Capture(['merge-base', target_branch, patch_rev])
Edward Lemurca7d8812018-07-24 17:42:45 +0000433 self.Print('Merge base of %s and %s is %s' % (
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000434 target_branch, patch_rev, merge_base))
Edward Lemurca7d8812018-07-24 17:42:45 +0000435 if merge_base == patch_rev:
436 # If the merge-base is patch_rev, it means patch_rev is already part
437 # of the history, so just check it out.
438 self._Capture(['checkout', patch_rev])
439 else:
440 # If a change was uploaded on top of another change, which has already
441 # landed, one of the commits in the cherry-pick range will be
442 # redundant, since it has already landed and its changes incorporated
443 # in the tree.
444 # We pass '--keep-redundant-commits' to ignore those changes.
445 self._Capture(['cherry-pick', merge_base + '..' + patch_rev,
446 '--keep-redundant-commits'])
447
448 if file_list is not None:
449 file_list.extend(self._GetDiffFilenames(base_rev))
450
451 except subprocess2.CalledProcessError as e:
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000452 self.Print('Failed to apply patch.')
453 self.Print('Patch ref is %r @ %r. Target branch for patch is %r. '
454 'Current HEAD is %r. Current dir is %r' % (
455 patch_repo, patch_ref, target_branch, base_rev,
456 self.checkout_path))
Edward Lemurca7d8812018-07-24 17:42:45 +0000457 self.Print('git returned non-zero exit status %s:\n%s' % (
458 e.returncode, e.stderr))
459 # Print the current status so that developers know what changes caused the
460 # patch failure, since git cherry-pick doesn't show that information.
461 self.Print(self._Capture(['status']))
John Budorick2c984a02018-07-18 23:24:13 +0000462 try:
Edward Lemurca7d8812018-07-24 17:42:45 +0000463 self._Capture(['cherry-pick', '--abort'])
464 except subprocess2.CalledProcessError:
465 pass
466 raise
467
Edward Lesmesc621b212018-03-21 20:26:56 -0400468 if options.reset_patch_ref:
469 self._Capture(['reset', '--soft', base_rev])
470
msb@chromium.orge28e4982009-09-25 20:51:45 +0000471 def update(self, options, args, file_list):
472 """Runs git to update or transparently checkout the working copy.
473
474 All updated files will be appended to file_list.
475
476 Raises:
477 Error: if can't get URL for relative path.
478 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000479 if args:
480 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
481
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000482 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000483
John Budorick882c91e2018-07-12 22:11:41 +0000484 # If a dependency is not pinned, track the default remote branch.
485 default_rev = 'refs/remotes/%s/master' % self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000486 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000487 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000488 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000489 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000490 # Override the revision number.
491 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000492 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000493 # Check again for a revision in case an initial ref was specified
494 # in the url, for example bla.git@refs/heads/custombranch
495 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000496 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000497 if not revision:
498 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000499
szager@chromium.org8a139702014-06-20 15:55:01 +0000500 if managed:
501 self._DisableHooks()
502
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000503 printed_path = False
504 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000505 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700506 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000507 verbose = ['--verbose']
508 printed_path = True
509
Edward Lemurdb5c5ad2019-03-07 16:00:24 +0000510 if revision.startswith('refs/branch-heads'):
511 options.with_branch_heads = True
512 if revision.startswith('refs/tags'):
513 options.with_tags = True
514
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
John Budorick882c91e2018-07-12 22:11:41 +0000528 mirror = self._GetMirror(url, options)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000529 if mirror:
530 url = mirror.mirror_path
531
primiano@chromium.org1c127382015-02-17 11:15:40 +0000532 # If we are going to introduce a new project, there is a possibility that
533 # we are syncing back to a state where the project was originally a
534 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
535 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
536 # In such case, we might have a backup of the former .git folder, which can
537 # be used to avoid re-fetching the entire repo again (useful for bisects).
538 backup_dir = self.GetGitBackupDirPath()
539 target_dir = os.path.join(self.checkout_path, '.git')
540 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
541 gclient_utils.safe_makedirs(self.checkout_path)
542 os.rename(backup_dir, target_dir)
543 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800544 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000545
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000546 if (not os.path.exists(self.checkout_path) or
547 (os.path.isdir(self.checkout_path) and
548 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000549 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000550 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000551 try:
John Budorick882c91e2018-07-12 22:11:41 +0000552 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000553 except subprocess2.CalledProcessError:
554 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000555 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000556 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800557 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20 +0000558 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
Raul Tambrec2f74c12019-03-19 05:55:53 +0000559 file_list.extend(
560 [os.path.join(self.checkout_path, f.decode()) for f in files])
John Budorick21a51b32018-09-19 19:39:20 +0000561 if mirror:
562 self._Capture(
563 ['remote', 'set-url', '--push', 'origin', mirror.url])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000564 if not verbose:
565 # Make the output a little prettier. It's nice to have some whitespace
566 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000567 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000568 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000569
John Budorick21a51b32018-09-19 19:39:20 +0000570 if mirror:
571 self._Capture(
572 ['remote', 'set-url', '--push', 'origin', mirror.url])
573
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000574 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000575 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000576 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
577 return self._Capture(['rev-parse', '--verify', 'HEAD'])
578
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000579 self._maybe_break_locks(options)
580
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000581 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000582 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000583
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000584 # See if the url has changed (the unittests use git://foo for the url, let
585 # that through).
586 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
587 return_early = False
588 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
589 # unit test pass. (and update the comment above)
590 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
591 # This allows devs to use experimental repos which have a different url
592 # but whose branch(s) are the same as official repos.
borenet@google.comb09097a2014-04-09 19:09:08 +0000593 if (current_url.rstrip('/') != url.rstrip('/') and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000594 url != 'git://foo' and
595 subprocess2.capture(
596 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
597 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000598 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000599 if not (options.force or options.reset):
600 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700601 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000602 # Switch over to the new upstream
603 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000604 if mirror:
605 with open(os.path.join(
606 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
607 'w') as fh:
608 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000609 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
610 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000611
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000612 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000613 else:
John Budorick882c91e2018-07-12 22:11:41 +0000614 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000615
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000616 if return_early:
617 return self._Capture(['rev-parse', '--verify', 'HEAD'])
618
msb@chromium.org5bde4852009-12-14 16:47:12 +0000619 cur_branch = self._GetCurrentBranch()
620
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000621 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000622 # 0) HEAD is detached. Probably from our initial clone.
623 # - make sure HEAD is contained by a named ref, then update.
624 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700625 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000626 # - try to rebase onto the new hash or branch
627 # 2) current branch is tracking a remote branch with local committed
628 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000629 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000630 # 3) current branch is tracking a remote branch w/or w/out changes, and
631 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000632 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000633 # 4) current branch is tracking a remote branch, but DEPS switches to a
634 # different remote branch, and
635 # a) current branch has no local changes, and --force:
636 # - checkout new branch
637 # b) current branch has local changes, and --force and --reset:
638 # - checkout new branch
639 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000640
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000641 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
642 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000643 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
644 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000645 if cur_branch is None:
646 upstream_branch = None
647 current_type = "detached"
648 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000649 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000650 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
651 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
652 current_type = "hash"
653 logging.debug("Current branch is not tracking an upstream (remote)"
654 " branch.")
655 elif upstream_branch.startswith('refs/remotes'):
656 current_type = "branch"
657 else:
658 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000659
Edward Lemur579c9862018-07-13 23:17:51 +0000660 self._SetFetchConfig(options)
Edward Lemur579c9862018-07-13 23:17:51 +0000661
Michael Spang73fac912019-03-08 18:44:19 +0000662 # Fetch upstream if we don't already have |revision|.
John Budorick882c91e2018-07-12 22:11:41 +0000663 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
Michael Spang73fac912019-03-08 18:44:19 +0000664 self._Fetch(options, prune=options.force)
665
666 if not scm.GIT.IsValidRevision(self.checkout_path, revision,
667 sha_only=True):
668 # Update the remotes first so we have all the refs.
669 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
670 cwd=self.checkout_path)
671 if verbose:
672 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000673
John Budorick882c91e2018-07-12 22:11:41 +0000674 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200675
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000676 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000677 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000678 target = 'HEAD'
679 if options.upstream and upstream_branch:
680 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800681 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000682
msb@chromium.org786fb682010-06-02 15:16:23 +0000683 if current_type == 'detached':
684 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800685 # We just did a Scrub, this is as clean as it's going to get. In
686 # particular if HEAD is a commit that contains two versions of the same
687 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
688 # to actually "Clean" the checkout; that commit is uncheckoutable on this
689 # system. The best we can do is carry forward to the checkout step.
690 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000691 self._CheckClean(revision)
692 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000693 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000694 self.Print('Up-to-date; skipping checkout.')
695 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000696 # 'git checkout' may need to overwrite existing untracked files. Allow
697 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000698 self._Checkout(
699 options,
John Budorick882c91e2018-07-12 22:11:41 +0000700 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000701 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000702 quiet=True,
703 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000704 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000705 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000706 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000707 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700708 # Can't find a merge-base since we don't know our upstream. That makes
709 # this command VERY likely to produce a rebase failure. For now we
710 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000711 upstream_branch = self.remote
712 if options.revision or deps_revision:
713 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700714 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700715 printed_path=printed_path, merge=options.merge)
716 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000717 elif rev_type == 'hash':
718 # case 2
719 self._AttemptRebase(upstream_branch, file_list, options,
720 newbase=revision, printed_path=printed_path,
721 merge=options.merge)
722 printed_path = True
723 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000724 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000725 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000726 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000727 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000728 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000729 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000730 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000731 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
732 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000733 force_switch = False
734 if options.force:
735 try:
John Budorick882c91e2018-07-12 22:11:41 +0000736 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000737 # case 4a
738 force_switch = True
739 except gclient_utils.Error as e:
740 if options.reset:
741 # case 4b
742 force_switch = True
743 else:
744 switch_error = '%s\n%s' % (e.message, switch_error)
745 if force_switch:
746 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000747 (upstream_branch, new_base))
748 switch_branch = 'gclient_' + remote_ref[1]
749 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000750 self._Checkout(options, switch_branch, force=True, quiet=True)
751 else:
752 # case 4c
753 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000754 else:
755 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800756 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000757 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000758 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000759 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000760 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000761 if options.merge:
762 merge_args.append('--ff')
763 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000764 merge_args.append('--ff-only')
765 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000766 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000767 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700768 rebase_files = []
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000769 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
770 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000771 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700772 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000773 printed_path = True
774 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000775 if not options.auto_rebase:
776 try:
777 action = self._AskForData(
778 'Cannot %s, attempt to rebase? '
779 '(y)es / (q)uit / (s)kip : ' %
780 ('merge' if options.merge else 'fast-forward merge'),
781 options)
782 except ValueError:
783 raise gclient_utils.Error('Invalid Character')
784 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700785 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000786 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000787 printed_path = True
788 break
789 elif re.match(r'quit|q', action, re.I):
790 raise gclient_utils.Error("Can't fast-forward, please merge or "
791 "rebase manually.\n"
792 "cd %s && git " % self.checkout_path
793 + "rebase %s" % upstream_branch)
794 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000795 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000796 return
797 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000798 self.Print('Input not recognized')
smut@google.com27c9c8a2014-09-11 19:57:55 +0000799 elif re.match("error: Your local changes to '.*' would be "
800 "overwritten by merge. Aborting.\nPlease, commit your "
801 "changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000802 e.stderr):
803 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000804 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700805 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000806 printed_path = True
807 raise gclient_utils.Error(e.stderr)
808 else:
809 # Some other problem happened with the merge
810 logging.error("Error during fast-forward merge in %s!" % self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000811 self.Print(e.stderr)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000812 raise
813 else:
814 # Fast-forward merge was successful
815 if not re.match('Already up-to-date.', merge_output) or verbose:
816 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700817 self.Print('_____ %s at %s' % (self.relpath, revision),
818 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000819 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000820 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000821 if not verbose:
822 # Make the output a little prettier. It's nice to have some
823 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000824 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000825
agablec3937b92016-10-25 10:13:03 -0700826 if file_list is not None:
827 file_list.extend(
828 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000829
830 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000831 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700832 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000833 '\nConflict while rebasing this branch.\n'
834 'Fix the conflict and run gclient again.\n'
835 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700836 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000837
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000838 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000839 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
840 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000841
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000842 # If --reset and --delete_unversioned_trees are specified, remove any
843 # untracked directories.
844 if options.reset and options.delete_unversioned_trees:
845 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
846 # merge-base by default), so doesn't include untracked files. So we use
847 # 'git ls-files --directory --others --exclude-standard' here directly.
848 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800849 ['-c', 'core.quotePath=false', 'ls-files',
850 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000851 self.checkout_path)
852 for path in (p for p in paths.splitlines() if p.endswith('/')):
853 full_path = os.path.join(self.checkout_path, path)
854 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000855 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000856 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000857
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000858 return self._Capture(['rev-parse', '--verify', 'HEAD'])
859
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000860 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000861 """Reverts local modifications.
862
863 All reverted files will be appended to file_list.
864 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000865 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000866 # revert won't work if the directory doesn't exist. It needs to
867 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000868 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000869 # Don't reuse the args.
870 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000871
872 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000873 if options.upstream:
874 if self._GetCurrentBranch():
875 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
876 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000877 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000878 if not deps_revision:
879 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000880 if deps_revision.startswith('refs/heads/'):
881 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700882 try:
883 deps_revision = self.GetUsableRev(deps_revision, options)
884 except NoUsableRevError as e:
885 # If the DEPS entry's url and hash changed, try to update the origin.
886 # See also http://crbug.com/520067.
887 logging.warn(
888 'Couldn\'t find usable revision, will retrying to update instead: %s',
889 e.message)
890 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000891
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000892 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800893 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000894
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800895 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000896 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000897
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000898 if file_list is not None:
899 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
900
901 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000902 """Returns revision"""
903 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000904
msb@chromium.orge28e4982009-09-25 20:51:45 +0000905 def runhooks(self, options, args, file_list):
906 self.status(options, args, file_list)
907
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000908 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000909 """Display status information."""
910 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000911 self.Print('________ couldn\'t run status in %s:\n'
912 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000913 else:
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700914 try:
915 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
916 except subprocess2.CalledProcessError:
917 merge_base = []
Aaron Gablef4068aa2017-12-12 15:14:09 -0800918 self._Run(
919 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
920 options, stdout=self.out_fh, always=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000921 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800922 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000923 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000924
smutae7ea312016-07-18 11:59:41 -0700925 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700926 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700927 sha1 = None
928 if not os.path.isdir(self.checkout_path):
929 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800930 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700931
932 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
933 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700934 else:
agable41e3a6c2016-10-20 11:36:56 -0700935 # May exist in origin, but we don't have it yet, so fetch and look
936 # again.
937 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700938 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
939 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700940
941 if not sha1:
942 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800943 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700944
945 return sha1
946
primiano@chromium.org1c127382015-02-17 11:15:40 +0000947 def GetGitBackupDirPath(self):
948 """Returns the path where the .git folder for the current project can be
949 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
950 return os.path.join(self._root_dir,
951 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
952
John Budorick882c91e2018-07-12 22:11:41 +0000953 def _GetMirror(self, url, options):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000954 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000955 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000956 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000957 mirror_kwargs = {
958 'print_func': self.filter,
John Budorick882c91e2018-07-12 22:11:41 +0000959 'refs': []
hinoka@google.comb1b54572014-04-16 22:29:23 +0000960 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000961 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
962 mirror_kwargs['refs'].append('refs/branch-heads/*')
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000963 if hasattr(options, 'with_tags') and options.with_tags:
964 mirror_kwargs['refs'].append('refs/tags/*')
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
1041 stdout = gclient_utils.WriteToStdout(self.out_fh)
1042 else:
1043 print_stdout = False
1044 stdout = self.out_fh
1045 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
1046 print_stdout=print_stdout, stdout=stdout)
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)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001120 except subprocess2.CalledProcessError, e:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001121 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
1122 re.match(r'cannot rebase: your index contains uncommitted changes',
1123 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):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001140 self.Print('%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001141 continue
1142 else:
1143 gclient_utils.Error("Input not recognized")
1144 continue
1145 elif re.search(r'^CONFLICT', e.stdout, re.M):
1146 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:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001150 self.Print(e.stdout.strip())
1151 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001152 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1153 "manually.\ncd %s && git " %
1154 self.checkout_path
1155 + "%s" % ' '.join(rebase_cmd))
1156
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001157 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001158 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001159 # Make the output a little prettier. It's nice to have some
1160 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001161 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001162
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001163 @staticmethod
1164 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001165 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1166 if not ok:
1167 raise gclient_utils.Error('git version %s < minimum required %s' %
1168 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001169
John Budorick882c91e2018-07-12 22:11:41 +00001170 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001171 # Special case handling if all 3 conditions are met:
1172 # * the mirros have recently changed, but deps destination remains same,
1173 # * the git histories of mirrors are conflicting.
1174 # * git cache is used
1175 # This manifests itself in current checkout having invalid HEAD commit on
1176 # most git operations. Since git cache is used, just deleted the .git
1177 # folder, and re-create it by cloning.
1178 try:
1179 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1180 except subprocess2.CalledProcessError as e:
1181 if ('fatal: bad object HEAD' in e.stderr
1182 and self.cache_dir and self.cache_dir in url):
1183 self.Print((
1184 'Likely due to DEPS change with git cache_dir, '
1185 'the current commit points to no longer existing object.\n'
1186 '%s' % e)
1187 )
1188 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001189 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001190 else:
1191 raise
1192
msb@chromium.org786fb682010-06-02 15:16:23 +00001193 def _IsRebasing(self):
1194 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1195 # have a plumbing command to determine whether a rebase is in progress, so
1196 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1197 g = os.path.join(self.checkout_path, '.git')
1198 return (
1199 os.path.isdir(os.path.join(g, "rebase-merge")) or
1200 os.path.isdir(os.path.join(g, "rebase-apply")))
1201
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001202 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001203 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1204 if os.path.exists(lockfile):
1205 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001206 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001207 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1208 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001209 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001210
msb@chromium.org786fb682010-06-02 15:16:23 +00001211 # Make sure the tree is clean; see git-rebase.sh for reference
1212 try:
1213 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001214 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001215 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001216 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001217 '\tYou have unstaged changes.\n'
1218 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001219 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001220 try:
1221 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001222 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001223 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001224 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001225 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001226 '\tYour index contains uncommitted changes\n'
1227 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001228 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001229
agable83faed02016-10-24 14:37:10 -07001230 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001231 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1232 # reference by a commit). If not, error out -- most likely a rebase is
1233 # in progress, try to detect so we can give a better error.
1234 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001235 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1236 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001237 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001238 # Commit is not contained by any rev. See if the user is rebasing:
1239 if self._IsRebasing():
1240 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001241 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001242 '\tAlready in a conflict, i.e. (no branch).\n'
1243 '\tFix the conflict and run gclient again.\n'
1244 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1245 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001246 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001247 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001248 name = ('saved-by-gclient-' +
1249 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001250 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001251 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001252 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001253
msb@chromium.org5bde4852009-12-14 16:47:12 +00001254 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001255 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001256 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001257 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001258 return None
1259 return branch
1260
borenet@google.comc3e09d22014-04-10 13:58:18 +00001261 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001262 kwargs.setdefault('cwd', self.checkout_path)
1263 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001264 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001265 env = scm.GIT.ApplyEnvVars(kwargs)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001266 ret = subprocess2.check_output(['git'] + args, env=env, **kwargs)
1267 if strip:
1268 ret = ret.strip()
1269 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001270
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001271 def _Checkout(self, options, ref, force=False, quiet=None):
1272 """Performs a 'git-checkout' operation.
1273
1274 Args:
1275 options: The configured option set
1276 ref: (str) The branch/commit to checkout
1277 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1278 'None', the behavior is inferred from 'options.verbose'.
1279 Returns: (str) The output of the checkout operation
1280 """
1281 if quiet is None:
1282 quiet = (not options.verbose)
1283 checkout_args = ['checkout']
1284 if force:
1285 checkout_args.append('--force')
1286 if quiet:
1287 checkout_args.append('--quiet')
1288 checkout_args.append(ref)
1289 return self._Capture(checkout_args)
1290
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001291 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1292 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001293 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemurd64781e2018-07-11 23:09:55 +00001294 # When a mirror is configured, it fetches only the refs/heads, and possibly
1295 # the refs/branch-heads and refs/tags, but not the refs/changes. So, if
1296 # we're asked to fetch a refs/changes ref from the mirror, it won't have it.
1297 # This makes sure that we always fetch refs/changes directly from the
1298 # repository and not from the mirror.
1299 if refspec and refspec.startswith('refs/changes'):
1300 remote, _ = gclient_utils.SplitUrlRevision(self.url)
1301 # Make sure that we fetch the (remote) refs/changes/xx ref to the (local)
1302 # refs/changes/xx ref.
1303 if ':' not in refspec:
1304 refspec += ':' + refspec
dnj@chromium.org680f2172014-06-25 00:39:32 +00001305 fetch_cmd = cfg + [
1306 'fetch',
1307 remote or self.remote,
1308 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001309 if refspec:
1310 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001311
1312 if prune:
1313 fetch_cmd.append('--prune')
1314 if options.verbose:
1315 fetch_cmd.append('--verbose')
1316 elif quiet:
1317 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001318 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001319
1320 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1321 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1322
Edward Lemur579c9862018-07-13 23:17:51 +00001323 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001324 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1325 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001326 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001327 try:
1328 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1329 options)
1330 self._Run(['config', 'remote.%s.fetch' % self.remote,
1331 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1332 except subprocess2.CalledProcessError as e:
1333 # If exit code was 5, it means we attempted to unset a config that
1334 # didn't exist. Ignore it.
1335 if e.returncode != 5:
1336 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001337 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001338 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001339 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1340 '^\\+refs/branch-heads/\\*:.*$']
1341 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001342 if hasattr(options, 'with_tags') and options.with_tags:
1343 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1344 '+refs/tags/*:refs/tags/*',
1345 '^\\+refs/tags/\\*:.*$']
1346 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001347
John Budorick882c91e2018-07-12 22:11:41 +00001348 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001349 """Attempts to fetch |revision| if not available in local repo.
1350
1351 Returns possibly updated revision."""
John Budorick882c91e2018-07-12 22:11:41 +00001352 try:
1353 self._Capture(['rev-parse', revision])
1354 except subprocess2.CalledProcessError:
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001355 self._Fetch(options, refspec=revision)
1356 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1357 return revision
1358
dnj@chromium.org680f2172014-06-25 00:39:32 +00001359 def _Run(self, args, options, show_header=True, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001360 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001361 kwargs.setdefault('cwd', self.checkout_path)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001362 kwargs.setdefault('stdout', self.out_fh)
szager@chromium.org848fd492014-04-09 19:06:44 +00001363 kwargs['filter_fn'] = self.filter
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001364 kwargs.setdefault('print_stdout', False)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001365 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001366 cmd = ['git'] + args
dnj@chromium.org680f2172014-06-25 00:39:32 +00001367 if show_header:
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001368 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1369 else:
1370 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(
1459 suffix='.ensure', delete=False) as ensure_file:
John Budorick302bb842018-07-17 23:49:17 +00001460 ensure_file.write('$ParanoidMode CheckPresence\n\n')
John Budorick0f7b2002018-01-19 15:46:17 -08001461 for subdir, packages in sorted(self._packages_by_subdir.iteritems()):
1462 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 ]
1481 gclient_utils.CheckCallAndFilterAndHeader(cmd)
1482
John Budorickd3ba72b2018-03-20 12:27:42 -07001483 def run(self, command):
1484 if command == 'update':
1485 self.ensure()
1486 elif command == 'revert':
1487 self.clobber()
1488 self.ensure()
1489
John Budorick0f7b2002018-01-19 15:46:17 -08001490 def created_package(self, package):
1491 """Checks whether this root created the given package.
1492
1493 Args:
1494 package: CipdPackage; the package to check.
1495 Returns:
1496 bool; whether this root created the given package.
1497 """
1498 return package in self._all_packages
1499
1500 @property
1501 def root_dir(self):
1502 return self._root_dir
1503
1504 @property
1505 def service_url(self):
1506 return self._service_url
1507
1508
1509class CipdWrapper(SCMWrapper):
1510 """Wrapper for CIPD.
1511
1512 Currently only supports chrome-infra-packages.appspot.com.
1513 """
John Budorick3929e9e2018-02-04 18:18:07 -08001514 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001515
1516 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1517 out_cb=None, root=None, package=None):
1518 super(CipdWrapper, self).__init__(
1519 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1520 out_cb=out_cb)
1521 assert root.created_package(package)
1522 self._package = package
1523 self._root = root
1524
1525 #override
1526 def GetCacheMirror(self):
1527 return None
1528
1529 #override
1530 def GetActualRemoteURL(self, options):
1531 return self._root.service_url
1532
1533 #override
1534 def DoesRemoteURLMatch(self, options):
1535 del options
1536 return True
1537
1538 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001539 """Does nothing.
1540
1541 CIPD packages should be reverted at the root by running
1542 `CipdRoot.run('revert')`.
1543 """
1544 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001545
1546 def diff(self, options, args, file_list):
1547 """CIPD has no notion of diffing."""
1548 pass
1549
1550 def pack(self, options, args, file_list):
1551 """CIPD has no notion of diffing."""
1552 pass
1553
1554 def revinfo(self, options, args, file_list):
1555 """Grab the instance ID."""
1556 try:
1557 tmpdir = tempfile.mkdtemp()
1558 describe_json_path = os.path.join(tmpdir, 'describe.json')
1559 cmd = [
1560 'cipd', 'describe',
1561 self._package.name,
1562 '-log-level', 'error',
1563 '-version', self._package.version,
1564 '-json-output', describe_json_path
1565 ]
1566 gclient_utils.CheckCallAndFilter(
1567 cmd, filter_fn=lambda _line: None, print_stdout=False)
1568 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