blob: 8714c9b1db21ff665c2bdc195a5f3779d568d3fd [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
96 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
Edward Lemur231f5ea2018-01-31 19:02:36 +010097 out_cb=None, print_outbuf=False):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000098 self.url = url
maruel@chromium.org5e73b0c2009-09-18 19:47:48 +000099 self._root_dir = root_dir
100 if self._root_dir:
101 self._root_dir = self._root_dir.replace('/', os.sep)
102 self.relpath = relpath
103 if self.relpath:
104 self.relpath = self.relpath.replace('/', os.sep)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000105 if self.relpath and self._root_dir:
106 self.checkout_path = os.path.join(self._root_dir, self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000107 if out_fh is None:
108 out_fh = sys.stdout
109 self.out_fh = out_fh
110 self.out_cb = out_cb
Edward Lemur231f5ea2018-01-31 19:02:36 +0100111 self.print_outbuf = print_outbuf
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000112
113 def Print(self, *args, **kwargs):
114 kwargs.setdefault('file', self.out_fh)
115 if kwargs.pop('timestamp', True):
116 self.out_fh.write('[%s] ' % gclient_utils.Elapsed())
117 print(*args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000118
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000119 def RunCommand(self, command, options, args, file_list=None):
agabledebf6c82016-12-21 12:50:12 -0800120 commands = ['update', 'updatesingle', 'revert',
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000121 'revinfo', 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000122
123 if not command in commands:
124 raise gclient_utils.Error('Unknown command %s' % command)
125
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000126 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000127 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000128 command, self.__class__.__name__))
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000129
130 return getattr(self, command)(options, args, file_list)
131
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000132 @staticmethod
133 def _get_first_remote_url(checkout_path):
134 log = scm.GIT.Capture(
135 ['config', '--local', '--get-regexp', r'remote.*.url'],
136 cwd=checkout_path)
137 # Get the second token of the first line of the log.
138 return log.splitlines()[0].split(' ', 1)[1]
139
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000140 def GetCacheMirror(self):
Robert Iannuccia19649b2018-06-29 16:31:45 +0000141 if getattr(self, 'cache_dir', None):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000142 url, _ = gclient_utils.SplitUrlRevision(self.url)
143 return git_cache.Mirror(url)
144 return None
145
smut@google.comd33eab32014-07-07 19:35:18 +0000146 def GetActualRemoteURL(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000147 """Attempt to determine the remote URL for this SCMWrapper."""
smut@google.comd33eab32014-07-07 19:35:18 +0000148 # Git
borenet@google.combda475e2014-03-24 19:04:45 +0000149 if os.path.exists(os.path.join(self.checkout_path, '.git')):
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000150 actual_remote_url = self._get_first_remote_url(self.checkout_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000151
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000152 mirror = self.GetCacheMirror()
153 # If the cache is used, obtain the actual remote URL from there.
154 if (mirror and mirror.exists() and
155 mirror.mirror_path.replace('\\', '/') ==
156 actual_remote_url.replace('\\', '/')):
157 actual_remote_url = self._get_first_remote_url(mirror.mirror_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000158 return actual_remote_url
borenet@google.com88d10082014-03-21 17:24:48 +0000159 return None
160
borenet@google.com4e9be262014-04-08 19:40:30 +0000161 def DoesRemoteURLMatch(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000162 """Determine whether the remote URL of this checkout is the expected URL."""
163 if not os.path.exists(self.checkout_path):
164 # A checkout which doesn't exist can't be broken.
165 return True
166
smut@google.comd33eab32014-07-07 19:35:18 +0000167 actual_remote_url = self.GetActualRemoteURL(options)
borenet@google.com88d10082014-03-21 17:24:48 +0000168 if actual_remote_url:
borenet@google.com8156c9f2014-04-01 16:41:36 +0000169 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
170 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
borenet@google.com88d10082014-03-21 17:24:48 +0000171 else:
172 # This may occur if the self.checkout_path exists but does not contain a
agable41e3a6c2016-10-20 11:36:56 -0700173 # valid git checkout.
borenet@google.com88d10082014-03-21 17:24:48 +0000174 return False
175
borenet@google.comb09097a2014-04-09 19:09:08 +0000176 def _DeleteOrMove(self, force):
177 """Delete the checkout directory or move it out of the way.
178
179 Args:
180 force: bool; if True, delete the directory. Otherwise, just move it.
181 """
borenet@google.comb2256212014-05-07 20:57:28 +0000182 if force and os.environ.get('CHROME_HEADLESS') == '1':
183 self.Print('_____ Conflicting directory found in %s. Removing.'
184 % self.checkout_path)
185 gclient_utils.AddWarning('Conflicting directory %s deleted.'
186 % self.checkout_path)
187 gclient_utils.rmtree(self.checkout_path)
188 else:
189 bad_scm_dir = os.path.join(self._root_dir, '_bad_scm',
190 os.path.dirname(self.relpath))
191
192 try:
193 os.makedirs(bad_scm_dir)
194 except OSError as e:
195 if e.errno != errno.EEXIST:
196 raise
197
198 dest_path = tempfile.mkdtemp(
199 prefix=os.path.basename(self.relpath),
200 dir=bad_scm_dir)
201 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
202 % (self.checkout_path, dest_path))
203 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
204 % (self.checkout_path, dest_path))
205 shutil.move(self.checkout_path, dest_path)
borenet@google.comb09097a2014-04-09 19:09:08 +0000206
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000207
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000208class GitWrapper(SCMWrapper):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000209 """Wrapper for Git"""
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000210 name = 'git'
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000211 remote = 'origin'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000212
Robert Iannuccia19649b2018-06-29 16:31:45 +0000213 @property
214 def cache_dir(self):
215 try:
216 return git_cache.Mirror.GetCachePath()
217 except RuntimeError:
218 return None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000219
John Budorick0f7b2002018-01-19 15:46:17 -0800220 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000221 """Removes 'git+' fake prefix from git URL."""
222 if url.startswith('git+http://') or url.startswith('git+https://'):
223 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800224 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000225 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
226 if self.out_cb:
227 filter_kwargs['predicate'] = self.out_cb
228 self.filter = gclient_utils.GitFilter(**filter_kwargs)
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000229
mukai@chromium.org9e3e82c2012-04-18 12:55:43 +0000230 @staticmethod
231 def BinaryExists():
232 """Returns true if the command exists."""
233 try:
234 # We assume git is newer than 1.7. See: crbug.com/114483
235 result, version = scm.GIT.AssertVersion('1.7')
236 if not result:
237 raise gclient_utils.Error('Git version is older than 1.7: %s' % version)
238 return result
239 except OSError:
240 return False
241
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000242 def GetCheckoutRoot(self):
243 return scm.GIT.GetCheckoutRoot(self.checkout_path)
244
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000245 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000246 """Returns the given revision's date in ISO-8601 format (which contains the
247 time zone)."""
248 # TODO(floitsch): get the time-stamp of the given revision and not just the
249 # time-stamp of the currently checked out revision.
250 return self._Capture(['log', '-n', '1', '--format=%ai'])
251
Aaron Gablef4068aa2017-12-12 15:14:09 -0800252 def _GetDiffFilenames(self, base):
253 """Returns the names of files modified since base."""
254 return self._Capture(
255 # Filter to remove base if it is None.
256 filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only', base])
257 ).split()
258
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000259 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 15:45:56 -0800260 _, revision = gclient_utils.SplitUrlRevision(self.url)
261 if not revision:
262 revision = 'refs/remotes/%s/master' % self.remote
263 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000264
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000265 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000266 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000267 repository.
268
269 The patch file is generated from a diff of the merge base of HEAD and
270 its upstream branch.
271 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700272 try:
273 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
274 except subprocess2.CalledProcessError:
275 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000276 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700277 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000278 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000279 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000280
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800281 def _Scrub(self, target, options):
282 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000283 quiet = []
284 if not options.verbose:
285 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800286 self._Run(['reset', '--hard', target] + quiet, options)
287 if options.force and options.delete_unversioned_trees:
288 # where `target` is a commit that contains both upper and lower case
289 # versions of the same file on a case insensitive filesystem, we are
290 # actually in a broken state here. The index will have both 'a' and 'A',
291 # but only one of them will exist on the disk. To progress, we delete
292 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800293 output = self._Capture([
294 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800295 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800296 # --porcelain (v1) looks like:
297 # XY filename
298 try:
299 filename = line[3:]
300 self.Print('_____ Deleting residual after reset: %r.' % filename)
301 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800302 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800303 except OSError:
304 pass
305
John Budorick882c91e2018-07-12 22:11:41 +0000306 def _FetchAndReset(self, revision, file_list, options):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800307 """Equivalent to git fetch; git reset."""
Edward Lemur579c9862018-07-13 23:17:51 +0000308 self._SetFetchConfig(options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000309
dnj@chromium.org680f2172014-06-25 00:39:32 +0000310 self._Fetch(options, prune=True, quiet=options.verbose)
John Budorick882c91e2018-07-12 22:11:41 +0000311 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000312 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800313 files = self._Capture(
314 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000315 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
316
szager@chromium.org8a139702014-06-20 15:55:01 +0000317 def _DisableHooks(self):
318 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
319 if not os.path.isdir(hook_dir):
320 return
321 for f in os.listdir(hook_dir):
322 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000323 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
324 if os.path.exists(disabled_hook_path):
325 os.remove(disabled_hook_path)
326 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000327
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000328 def _maybe_break_locks(self, options):
329 """This removes all .lock files from this repo's .git directory, if the
330 user passed the --break_repo_locks command line flag.
331
332 In particular, this will cleanup index.lock files, as well as ref lock
333 files.
334 """
335 if options.break_repo_locks:
336 git_dir = os.path.join(self.checkout_path, '.git')
337 for path, _, filenames in os.walk(git_dir):
338 for filename in filenames:
339 if filename.endswith('.lock'):
340 to_break = os.path.join(path, filename)
341 self.Print('breaking lock: %s' % (to_break,))
342 try:
343 os.remove(to_break)
344 except OSError as ex:
345 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
346 raise
347
Edward Lemurfb78b362018-07-16 22:33:34 +0000348 def _GetBranchForCommit(self, commit):
349 """Get the remote branch a commit is part of."""
350 if scm.GIT.IsAncestor(self.checkout_path, commit,
351 'refs/remotes/origin/master'):
352 return 'refs/remotes/origin/master'
353 remote_refs = self._Capture(
354 ['for-each-ref', 'refs/remotes/%s/*' % self.remote,
355 '--format=%(refname)']).splitlines()
356 for ref in remote_refs:
357 if scm.GIT.IsAncestor(self.checkout_path, commit, ref):
358 return ref
359 self.Print('Failed to find a remote ref that contains %s. '
360 'Candidate refs were %s.' % (commit, remote_refs))
361 # Fallback to the commit we got.
362 # This means that apply_path_ref will try to find the merge-base between the
363 # patch and the commit (which is most likely the commit) and cherry-pick
364 # everything in between.
365 return commit
366
Edward Lesmesc621b212018-03-21 20:26:56 -0400367 def apply_patch_ref(self, patch_repo, patch_ref, options, file_list):
368 base_rev = self._Capture(['rev-parse', 'HEAD'])
Edward Lemurfb78b362018-07-16 22:33:34 +0000369 branch_rev = self._GetBranchForCommit(base_rev)
Edward Lesmesc621b212018-03-21 20:26:56 -0400370 self.Print('===Applying patch ref===')
Edward Lemurfb78b362018-07-16 22:33:34 +0000371 self.Print('Repo is %r @ %r, ref is %r (%r), root is %r' % (
372 patch_repo, patch_ref, base_rev, branch_rev, self.checkout_path))
Edward Lesmesc621b212018-03-21 20:26:56 -0400373 self._Capture(['reset', '--hard'])
374 self._Capture(['fetch', patch_repo, patch_ref])
Edward Lemurfb78b362018-07-16 22:33:34 +0000375 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400376
Edward Lemurfb78b362018-07-16 22:33:34 +0000377 try:
378 if not options.rebase_patch_ref:
379 self._Capture(['checkout', patch_rev])
380 else:
381 # Find the merge-base between the branch_rev and patch_rev to find out
382 # the changes we need to cherry-pick on top of base_rev.
383 merge_base = self._Capture(['merge-base', branch_rev, patch_rev])
384 self.Print('Merge base of %s and %s is %s' % (
385 branch_rev, patch_rev, merge_base))
386 if merge_base == patch_rev:
387 # IF the merge-base is patch_rev, it means patch_rev is already part
388 # of the history, so just check it out.
389 self._Capture(['checkout', patch_rev])
390 else:
391 self._Capture(['cherry-pick', merge_base + '..' + patch_rev])
392
393 if file_list is not None:
394 file_list.extend(self._GetDiffFilenames(base_rev))
395
396 except subprocess2.CalledProcessError as e:
397 self.Print('Failed to apply %r @ %r to %r at %r' % (
398 patch_repo, patch_ref, base_rev, self.checkout_path))
399 self.Print('git returned non-zero exit status %s:\n%s' % (
400 e.returncode, e.stderr))
401 raise
402
Edward Lesmesc621b212018-03-21 20:26:56 -0400403 if options.reset_patch_ref:
404 self._Capture(['reset', '--soft', base_rev])
405
msb@chromium.orge28e4982009-09-25 20:51:45 +0000406 def update(self, options, args, file_list):
407 """Runs git to update or transparently checkout the working copy.
408
409 All updated files will be appended to file_list.
410
411 Raises:
412 Error: if can't get URL for relative path.
413 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000414 if args:
415 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
416
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000417 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000418
John Budorick882c91e2018-07-12 22:11:41 +0000419 # If a dependency is not pinned, track the default remote branch.
420 default_rev = 'refs/remotes/%s/master' % self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000421 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000422 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000423 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000424 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000425 # Override the revision number.
426 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000427 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000428 # Check again for a revision in case an initial ref was specified
429 # in the url, for example bla.git@refs/heads/custombranch
430 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000431 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000432 if not revision:
433 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000434
szager@chromium.org8a139702014-06-20 15:55:01 +0000435 if managed:
436 self._DisableHooks()
437
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000438 printed_path = False
439 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000440 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700441 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000442 verbose = ['--verbose']
443 printed_path = True
444
John Budorick882c91e2018-07-12 22:11:41 +0000445 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
446 if remote_ref:
447 # Rewrite remote refs to their local equivalents.
448 revision = ''.join(remote_ref)
449 rev_type = "branch"
450 elif revision.startswith('refs/'):
451 # Local branch? We probably don't want to support, since DEPS should
452 # always specify branches as they are in the upstream repo.
453 rev_type = "branch"
454 else:
455 # hash is also a tag, only make a distinction at checkout
456 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000457
John Budorick882c91e2018-07-12 22:11:41 +0000458 mirror = self._GetMirror(url, options)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000459 if mirror:
460 url = mirror.mirror_path
461
primiano@chromium.org1c127382015-02-17 11:15:40 +0000462 # If we are going to introduce a new project, there is a possibility that
463 # we are syncing back to a state where the project was originally a
464 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
465 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
466 # In such case, we might have a backup of the former .git folder, which can
467 # be used to avoid re-fetching the entire repo again (useful for bisects).
468 backup_dir = self.GetGitBackupDirPath()
469 target_dir = os.path.join(self.checkout_path, '.git')
470 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
471 gclient_utils.safe_makedirs(self.checkout_path)
472 os.rename(backup_dir, target_dir)
473 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800474 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000475
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000476 if (not os.path.exists(self.checkout_path) or
477 (os.path.isdir(self.checkout_path) and
478 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000479 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000480 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000481 try:
John Budorick882c91e2018-07-12 22:11:41 +0000482 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000483 except subprocess2.CalledProcessError:
484 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000485 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000486 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800487 files = self._Capture(
488 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000489 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000490 if not verbose:
491 # Make the output a little prettier. It's nice to have some whitespace
492 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000493 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000494 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000495
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000496 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000497 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000498 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
499 return self._Capture(['rev-parse', '--verify', 'HEAD'])
500
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000501 self._maybe_break_locks(options)
502
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000503 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000504 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000505
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000506 # See if the url has changed (the unittests use git://foo for the url, let
507 # that through).
508 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
509 return_early = False
510 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
511 # unit test pass. (and update the comment above)
512 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
513 # This allows devs to use experimental repos which have a different url
514 # but whose branch(s) are the same as official repos.
borenet@google.comb09097a2014-04-09 19:09:08 +0000515 if (current_url.rstrip('/') != url.rstrip('/') and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000516 url != 'git://foo' and
517 subprocess2.capture(
518 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
519 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000520 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000521 if not (options.force or options.reset):
522 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700523 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000524 # Switch over to the new upstream
525 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000526 if mirror:
527 with open(os.path.join(
528 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
529 'w') as fh:
530 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000531 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
532 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000533
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000534 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000535 else:
John Budorick882c91e2018-07-12 22:11:41 +0000536 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000537
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000538 if return_early:
539 return self._Capture(['rev-parse', '--verify', 'HEAD'])
540
msb@chromium.org5bde4852009-12-14 16:47:12 +0000541 cur_branch = self._GetCurrentBranch()
542
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000543 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000544 # 0) HEAD is detached. Probably from our initial clone.
545 # - make sure HEAD is contained by a named ref, then update.
546 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700547 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000548 # - try to rebase onto the new hash or branch
549 # 2) current branch is tracking a remote branch with local committed
550 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000551 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000552 # 3) current branch is tracking a remote branch w/or w/out changes, and
553 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000554 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000555 # 4) current branch is tracking a remote branch, but DEPS switches to a
556 # different remote branch, and
557 # a) current branch has no local changes, and --force:
558 # - checkout new branch
559 # b) current branch has local changes, and --force and --reset:
560 # - checkout new branch
561 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000562
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000563 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
564 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000565 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
566 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000567 if cur_branch is None:
568 upstream_branch = None
569 current_type = "detached"
570 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000571 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000572 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
573 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
574 current_type = "hash"
575 logging.debug("Current branch is not tracking an upstream (remote)"
576 " branch.")
577 elif upstream_branch.startswith('refs/remotes'):
578 current_type = "branch"
579 else:
580 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000581
Edward Lemur579c9862018-07-13 23:17:51 +0000582 self._SetFetchConfig(options)
583 self._Fetch(options, prune=options.force)
584
John Budorick882c91e2018-07-12 22:11:41 +0000585 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000586 # Update the remotes first so we have all the refs.
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000587 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000588 cwd=self.checkout_path)
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000589 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000590 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000591
John Budorick882c91e2018-07-12 22:11:41 +0000592 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200593
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000594 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000595 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000596 target = 'HEAD'
597 if options.upstream and upstream_branch:
598 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800599 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000600
msb@chromium.org786fb682010-06-02 15:16:23 +0000601 if current_type == 'detached':
602 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800603 # We just did a Scrub, this is as clean as it's going to get. In
604 # particular if HEAD is a commit that contains two versions of the same
605 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
606 # to actually "Clean" the checkout; that commit is uncheckoutable on this
607 # system. The best we can do is carry forward to the checkout step.
608 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000609 self._CheckClean(revision)
610 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000611 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000612 self.Print('Up-to-date; skipping checkout.')
613 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000614 # 'git checkout' may need to overwrite existing untracked files. Allow
615 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000616 self._Checkout(
617 options,
John Budorick882c91e2018-07-12 22:11:41 +0000618 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000619 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000620 quiet=True,
621 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000622 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000623 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000624 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000625 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700626 # Can't find a merge-base since we don't know our upstream. That makes
627 # this command VERY likely to produce a rebase failure. For now we
628 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000629 upstream_branch = self.remote
630 if options.revision or deps_revision:
631 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700632 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700633 printed_path=printed_path, merge=options.merge)
634 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000635 elif rev_type == 'hash':
636 # case 2
637 self._AttemptRebase(upstream_branch, file_list, options,
638 newbase=revision, printed_path=printed_path,
639 merge=options.merge)
640 printed_path = True
641 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000642 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000643 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000644 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000645 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000646 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000647 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000648 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000649 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
650 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000651 force_switch = False
652 if options.force:
653 try:
John Budorick882c91e2018-07-12 22:11:41 +0000654 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000655 # case 4a
656 force_switch = True
657 except gclient_utils.Error as e:
658 if options.reset:
659 # case 4b
660 force_switch = True
661 else:
662 switch_error = '%s\n%s' % (e.message, switch_error)
663 if force_switch:
664 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000665 (upstream_branch, new_base))
666 switch_branch = 'gclient_' + remote_ref[1]
667 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000668 self._Checkout(options, switch_branch, force=True, quiet=True)
669 else:
670 # case 4c
671 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000672 else:
673 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800674 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000675 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000676 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000677 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000678 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000679 if options.merge:
680 merge_args.append('--ff')
681 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000682 merge_args.append('--ff-only')
683 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000684 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000685 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700686 rebase_files = []
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000687 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
688 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000689 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700690 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000691 printed_path = True
692 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000693 if not options.auto_rebase:
694 try:
695 action = self._AskForData(
696 'Cannot %s, attempt to rebase? '
697 '(y)es / (q)uit / (s)kip : ' %
698 ('merge' if options.merge else 'fast-forward merge'),
699 options)
700 except ValueError:
701 raise gclient_utils.Error('Invalid Character')
702 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700703 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000704 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000705 printed_path = True
706 break
707 elif re.match(r'quit|q', action, re.I):
708 raise gclient_utils.Error("Can't fast-forward, please merge or "
709 "rebase manually.\n"
710 "cd %s && git " % self.checkout_path
711 + "rebase %s" % upstream_branch)
712 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000713 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000714 return
715 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000716 self.Print('Input not recognized')
smut@google.com27c9c8a2014-09-11 19:57:55 +0000717 elif re.match("error: Your local changes to '.*' would be "
718 "overwritten by merge. Aborting.\nPlease, commit your "
719 "changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000720 e.stderr):
721 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000722 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700723 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000724 printed_path = True
725 raise gclient_utils.Error(e.stderr)
726 else:
727 # Some other problem happened with the merge
728 logging.error("Error during fast-forward merge in %s!" % self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000729 self.Print(e.stderr)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000730 raise
731 else:
732 # Fast-forward merge was successful
733 if not re.match('Already up-to-date.', merge_output) or verbose:
734 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700735 self.Print('_____ %s at %s' % (self.relpath, revision),
736 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000737 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000738 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000739 if not verbose:
740 # Make the output a little prettier. It's nice to have some
741 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000742 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000743
agablec3937b92016-10-25 10:13:03 -0700744 if file_list is not None:
745 file_list.extend(
746 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000747
748 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000749 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700750 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000751 '\nConflict while rebasing this branch.\n'
752 'Fix the conflict and run gclient again.\n'
753 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700754 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000755
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000756 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000757 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
758 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000759
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000760 # If --reset and --delete_unversioned_trees are specified, remove any
761 # untracked directories.
762 if options.reset and options.delete_unversioned_trees:
763 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
764 # merge-base by default), so doesn't include untracked files. So we use
765 # 'git ls-files --directory --others --exclude-standard' here directly.
766 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800767 ['-c', 'core.quotePath=false', 'ls-files',
768 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000769 self.checkout_path)
770 for path in (p for p in paths.splitlines() if p.endswith('/')):
771 full_path = os.path.join(self.checkout_path, path)
772 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000773 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000774 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000775
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000776 return self._Capture(['rev-parse', '--verify', 'HEAD'])
777
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000778 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000779 """Reverts local modifications.
780
781 All reverted files will be appended to file_list.
782 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000783 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000784 # revert won't work if the directory doesn't exist. It needs to
785 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000786 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000787 # Don't reuse the args.
788 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000789
790 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000791 if options.upstream:
792 if self._GetCurrentBranch():
793 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
794 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000795 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000796 if not deps_revision:
797 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000798 if deps_revision.startswith('refs/heads/'):
799 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700800 try:
801 deps_revision = self.GetUsableRev(deps_revision, options)
802 except NoUsableRevError as e:
803 # If the DEPS entry's url and hash changed, try to update the origin.
804 # See also http://crbug.com/520067.
805 logging.warn(
806 'Couldn\'t find usable revision, will retrying to update instead: %s',
807 e.message)
808 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000809
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000810 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800811 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000812
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800813 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000814 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000815
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000816 if file_list is not None:
817 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
818
819 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000820 """Returns revision"""
821 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000822
msb@chromium.orge28e4982009-09-25 20:51:45 +0000823 def runhooks(self, options, args, file_list):
824 self.status(options, args, file_list)
825
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000826 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000827 """Display status information."""
828 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000829 self.Print('________ couldn\'t run status in %s:\n'
830 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000831 else:
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700832 try:
833 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
834 except subprocess2.CalledProcessError:
835 merge_base = []
Aaron Gablef4068aa2017-12-12 15:14:09 -0800836 self._Run(
837 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
838 options, stdout=self.out_fh, always=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000839 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800840 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000841 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000842
smutae7ea312016-07-18 11:59:41 -0700843 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700844 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700845 sha1 = None
846 if not os.path.isdir(self.checkout_path):
847 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800848 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700849
850 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
851 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700852 else:
agable41e3a6c2016-10-20 11:36:56 -0700853 # May exist in origin, but we don't have it yet, so fetch and look
854 # again.
855 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700856 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
857 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700858
859 if not sha1:
860 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800861 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700862
863 return sha1
864
primiano@chromium.org1c127382015-02-17 11:15:40 +0000865 def GetGitBackupDirPath(self):
866 """Returns the path where the .git folder for the current project can be
867 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
868 return os.path.join(self._root_dir,
869 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
870
John Budorick882c91e2018-07-12 22:11:41 +0000871 def _GetMirror(self, url, options):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000872 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000873 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000874 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000875 mirror_kwargs = {
876 'print_func': self.filter,
John Budorick882c91e2018-07-12 22:11:41 +0000877 'refs': []
hinoka@google.comb1b54572014-04-16 22:29:23 +0000878 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000879 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
880 mirror_kwargs['refs'].append('refs/branch-heads/*')
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000881 if hasattr(options, 'with_tags') and options.with_tags:
882 mirror_kwargs['refs'].append('refs/tags/*')
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000883 return git_cache.Mirror(url, **mirror_kwargs)
884
John Budorick882c91e2018-07-12 22:11:41 +0000885 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800886 """Update a git mirror by fetching the latest commits from the remote,
887 unless mirror already contains revision whose type is sha1 hash.
888 """
John Budorick882c91e2018-07-12 22:11:41 +0000889 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800890 if options.verbose:
891 self.Print('skipping mirror update, it has rev=%s already' % revision,
892 timestamp=False)
893 return
894
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000895 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000896 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000897 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000898 depth = 10
899 else:
900 depth = 10000
901 else:
902 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000903 mirror.populate(verbose=options.verbose,
904 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000905 depth=depth,
906 ignore_lock=getattr(options, 'ignore_locks', False),
907 lock_timeout=getattr(options, 'lock_timeout', 0))
908 mirror.unlock()
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000909
John Budorick882c91e2018-07-12 22:11:41 +0000910 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000911 """Clone a git repository from the given URL.
912
msb@chromium.org786fb682010-06-02 15:16:23 +0000913 Once we've cloned the repo, we checkout a working branch if the specified
914 revision is a branch head. If it is a tag or a specific commit, then we
915 leave HEAD detached as it makes future updates simpler -- in this case the
916 user should first create a new branch or switch to an existing branch before
917 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000918 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000919 # git clone doesn't seem to insert a newline properly before printing
920 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000921 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +0000922 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +0000923 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000924 if self.cache_dir:
925 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000926 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000927 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000928 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +0000929 # If the parent directory does not exist, Git clone on Windows will not
930 # create it, so we need to do it manually.
931 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000932 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000933
934 template_dir = None
935 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +0000936 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000937 # In the case of a subproject, the pinned sha is not necessarily the
938 # head of the remote branch (so we can't just use --depth=N). Instead,
939 # we tell git to fetch all the remote objects from SHA..HEAD by means of
940 # a template git dir which has a 'shallow' file pointing to the sha.
941 template_dir = tempfile.mkdtemp(
942 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
943 dir=parent_dir)
944 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
945 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
946 template_file.write(revision)
947 clone_cmd.append('--template=' + template_dir)
948 else:
949 # Otherwise, we're just interested in the HEAD. Just use --depth.
950 clone_cmd.append('--depth=1')
951
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000952 tmp_dir = tempfile.mkdtemp(
953 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
954 dir=parent_dir)
955 try:
956 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +0100957 if self.print_outbuf:
958 print_stdout = True
959 stdout = gclient_utils.WriteToStdout(self.out_fh)
960 else:
961 print_stdout = False
962 stdout = self.out_fh
963 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
964 print_stdout=print_stdout, stdout=stdout)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000965 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000966 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
967 os.path.join(self.checkout_path, '.git'))
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000968 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000969 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000970 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000971 finally:
972 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000973 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000974 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000975 if template_dir:
976 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +0000977 self._SetFetchConfig(options)
978 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000979 revision = self._AutoFetchRef(options, revision)
980 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
981 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000982 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +0000983 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000984 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +0000985 ('Checked out %s to a detached HEAD. Before making any commits\n'
986 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
987 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +0000988 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000989
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000990 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000991 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000992 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000993 raise gclient_utils.Error("Background task requires input. Rerun "
994 "gclient with --jobs=1 so that\n"
995 "interaction is possible.")
996 try:
997 return raw_input(prompt)
998 except KeyboardInterrupt:
999 # Hide the exception.
1000 sys.exit(1)
1001
1002
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001003 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001004 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001005 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001006 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001007 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001008 revision = upstream
1009 if newbase:
1010 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001011 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001012 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001013 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001014 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001015 printed_path = True
1016 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001017 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001018
1019 if merge:
1020 merge_output = self._Capture(['merge', revision])
1021 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001022 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001023 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001024
1025 # Build the rebase command here using the args
1026 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1027 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001028 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001029 rebase_cmd.append('--verbose')
1030 if newbase:
1031 rebase_cmd.extend(['--onto', newbase])
1032 rebase_cmd.append(upstream)
1033 if branch:
1034 rebase_cmd.append(branch)
1035
1036 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001037 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001038 except subprocess2.CalledProcessError, e:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001039 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
1040 re.match(r'cannot rebase: your index contains uncommitted changes',
1041 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001042 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001043 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001044 'Cannot rebase because of unstaged changes.\n'
1045 '\'git reset --hard HEAD\' ?\n'
1046 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001047 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001048 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001049 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001050 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001051 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001052 break
1053 elif re.match(r'quit|q', rebase_action, re.I):
1054 raise gclient_utils.Error("Please merge or rebase manually\n"
1055 "cd %s && git " % self.checkout_path
1056 + "%s" % ' '.join(rebase_cmd))
1057 elif re.match(r'show|s', rebase_action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001058 self.Print('%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001059 continue
1060 else:
1061 gclient_utils.Error("Input not recognized")
1062 continue
1063 elif re.search(r'^CONFLICT', e.stdout, re.M):
1064 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1065 "Fix the conflict and run gclient again.\n"
1066 "See 'man git-rebase' for details.\n")
1067 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001068 self.Print(e.stdout.strip())
1069 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001070 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1071 "manually.\ncd %s && git " %
1072 self.checkout_path
1073 + "%s" % ' '.join(rebase_cmd))
1074
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001075 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001076 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001077 # Make the output a little prettier. It's nice to have some
1078 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001079 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001080
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001081 @staticmethod
1082 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001083 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1084 if not ok:
1085 raise gclient_utils.Error('git version %s < minimum required %s' %
1086 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001087
John Budorick882c91e2018-07-12 22:11:41 +00001088 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001089 # Special case handling if all 3 conditions are met:
1090 # * the mirros have recently changed, but deps destination remains same,
1091 # * the git histories of mirrors are conflicting.
1092 # * git cache is used
1093 # This manifests itself in current checkout having invalid HEAD commit on
1094 # most git operations. Since git cache is used, just deleted the .git
1095 # folder, and re-create it by cloning.
1096 try:
1097 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1098 except subprocess2.CalledProcessError as e:
1099 if ('fatal: bad object HEAD' in e.stderr
1100 and self.cache_dir and self.cache_dir in url):
1101 self.Print((
1102 'Likely due to DEPS change with git cache_dir, '
1103 'the current commit points to no longer existing object.\n'
1104 '%s' % e)
1105 )
1106 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001107 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001108 else:
1109 raise
1110
msb@chromium.org786fb682010-06-02 15:16:23 +00001111 def _IsRebasing(self):
1112 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1113 # have a plumbing command to determine whether a rebase is in progress, so
1114 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1115 g = os.path.join(self.checkout_path, '.git')
1116 return (
1117 os.path.isdir(os.path.join(g, "rebase-merge")) or
1118 os.path.isdir(os.path.join(g, "rebase-apply")))
1119
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001120 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001121 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1122 if os.path.exists(lockfile):
1123 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001124 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001125 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1126 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001127 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001128
msb@chromium.org786fb682010-06-02 15:16:23 +00001129 # Make sure the tree is clean; see git-rebase.sh for reference
1130 try:
1131 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001132 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001133 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001134 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001135 '\tYou have unstaged changes.\n'
1136 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001137 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001138 try:
1139 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001140 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001141 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001142 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001143 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001144 '\tYour index contains uncommitted changes\n'
1145 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001146 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001147
agable83faed02016-10-24 14:37:10 -07001148 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001149 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1150 # reference by a commit). If not, error out -- most likely a rebase is
1151 # in progress, try to detect so we can give a better error.
1152 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001153 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1154 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001155 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001156 # Commit is not contained by any rev. See if the user is rebasing:
1157 if self._IsRebasing():
1158 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001159 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001160 '\tAlready in a conflict, i.e. (no branch).\n'
1161 '\tFix the conflict and run gclient again.\n'
1162 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1163 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001164 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001165 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001166 name = ('saved-by-gclient-' +
1167 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001168 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001169 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001170 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001171
msb@chromium.org5bde4852009-12-14 16:47:12 +00001172 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001173 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001174 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001175 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001176 return None
1177 return branch
1178
borenet@google.comc3e09d22014-04-10 13:58:18 +00001179 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001180 kwargs.setdefault('cwd', self.checkout_path)
1181 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001182 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001183 env = scm.GIT.ApplyEnvVars(kwargs)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001184 ret = subprocess2.check_output(['git'] + args, env=env, **kwargs)
1185 if strip:
1186 ret = ret.strip()
1187 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001188
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001189 def _Checkout(self, options, ref, force=False, quiet=None):
1190 """Performs a 'git-checkout' operation.
1191
1192 Args:
1193 options: The configured option set
1194 ref: (str) The branch/commit to checkout
1195 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1196 'None', the behavior is inferred from 'options.verbose'.
1197 Returns: (str) The output of the checkout operation
1198 """
1199 if quiet is None:
1200 quiet = (not options.verbose)
1201 checkout_args = ['checkout']
1202 if force:
1203 checkout_args.append('--force')
1204 if quiet:
1205 checkout_args.append('--quiet')
1206 checkout_args.append(ref)
1207 return self._Capture(checkout_args)
1208
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001209 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1210 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001211 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemurd64781e2018-07-11 23:09:55 +00001212 # When a mirror is configured, it fetches only the refs/heads, and possibly
1213 # the refs/branch-heads and refs/tags, but not the refs/changes. So, if
1214 # we're asked to fetch a refs/changes ref from the mirror, it won't have it.
1215 # This makes sure that we always fetch refs/changes directly from the
1216 # repository and not from the mirror.
1217 if refspec and refspec.startswith('refs/changes'):
1218 remote, _ = gclient_utils.SplitUrlRevision(self.url)
1219 # Make sure that we fetch the (remote) refs/changes/xx ref to the (local)
1220 # refs/changes/xx ref.
1221 if ':' not in refspec:
1222 refspec += ':' + refspec
dnj@chromium.org680f2172014-06-25 00:39:32 +00001223 fetch_cmd = cfg + [
1224 'fetch',
1225 remote or self.remote,
1226 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001227 if refspec:
1228 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001229
1230 if prune:
1231 fetch_cmd.append('--prune')
1232 if options.verbose:
1233 fetch_cmd.append('--verbose')
1234 elif quiet:
1235 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001236 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001237
1238 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1239 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1240
Edward Lemur579c9862018-07-13 23:17:51 +00001241 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001242 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1243 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001244 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001245 try:
1246 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1247 options)
1248 self._Run(['config', 'remote.%s.fetch' % self.remote,
1249 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1250 except subprocess2.CalledProcessError as e:
1251 # If exit code was 5, it means we attempted to unset a config that
1252 # didn't exist. Ignore it.
1253 if e.returncode != 5:
1254 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001255 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001256 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001257 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1258 '^\\+refs/branch-heads/\\*:.*$']
1259 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001260 if hasattr(options, 'with_tags') and options.with_tags:
1261 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1262 '+refs/tags/*:refs/tags/*',
1263 '^\\+refs/tags/\\*:.*$']
1264 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001265
John Budorick882c91e2018-07-12 22:11:41 +00001266 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001267 """Attempts to fetch |revision| if not available in local repo.
1268
1269 Returns possibly updated revision."""
John Budorick882c91e2018-07-12 22:11:41 +00001270 try:
1271 self._Capture(['rev-parse', revision])
1272 except subprocess2.CalledProcessError:
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001273 self._Fetch(options, refspec=revision)
1274 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1275 return revision
1276
dnj@chromium.org680f2172014-06-25 00:39:32 +00001277 def _Run(self, args, options, show_header=True, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001278 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001279 kwargs.setdefault('cwd', self.checkout_path)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001280 kwargs.setdefault('stdout', self.out_fh)
szager@chromium.org848fd492014-04-09 19:06:44 +00001281 kwargs['filter_fn'] = self.filter
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001282 kwargs.setdefault('print_stdout', False)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001283 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001284 cmd = ['git'] + args
dnj@chromium.org680f2172014-06-25 00:39:32 +00001285 if show_header:
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001286 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1287 else:
1288 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001289
1290
1291class CipdPackage(object):
1292 """A representation of a single CIPD package."""
1293
John Budorickd3ba72b2018-03-20 12:27:42 -07001294 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001295 self._authority_for_subdir = authority_for_subdir
1296 self._name = name
1297 self._version = version
1298
1299 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001300 def authority_for_subdir(self):
1301 """Whether this package has authority to act on behalf of its subdir.
1302
1303 Some operations should only be performed once per subdirectory. A package
1304 that has authority for its subdirectory is the only package that should
1305 perform such operations.
1306
1307 Returns:
1308 bool; whether this package has subdir authority.
1309 """
1310 return self._authority_for_subdir
1311
1312 @property
1313 def name(self):
1314 return self._name
1315
1316 @property
1317 def version(self):
1318 return self._version
1319
1320
1321class CipdRoot(object):
1322 """A representation of a single CIPD root."""
1323 def __init__(self, root_dir, service_url):
1324 self._all_packages = set()
1325 self._mutator_lock = threading.Lock()
1326 self._packages_by_subdir = collections.defaultdict(list)
1327 self._root_dir = root_dir
1328 self._service_url = service_url
1329
1330 def add_package(self, subdir, package, version):
1331 """Adds a package to this CIPD root.
1332
1333 As far as clients are concerned, this grants both root and subdir authority
1334 to packages arbitrarily. (The implementation grants root authority to the
1335 first package added and subdir authority to the first package added for that
1336 subdir, but clients should not depend on or expect that behavior.)
1337
1338 Args:
1339 subdir: str; relative path to where the package should be installed from
1340 the cipd root directory.
1341 package: str; the cipd package name.
1342 version: str; the cipd package version.
1343 Returns:
1344 CipdPackage; the package that was created and added to this root.
1345 """
1346 with self._mutator_lock:
1347 cipd_package = CipdPackage(
1348 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001349 not self._packages_by_subdir[subdir])
1350 self._all_packages.add(cipd_package)
1351 self._packages_by_subdir[subdir].append(cipd_package)
1352 return cipd_package
1353
1354 def packages(self, subdir):
1355 """Get the list of configured packages for the given subdir."""
1356 return list(self._packages_by_subdir[subdir])
1357
1358 def clobber(self):
1359 """Remove the .cipd directory.
1360
1361 This is useful for forcing ensure to redownload and reinitialize all
1362 packages.
1363 """
1364 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001365 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001366 try:
1367 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1368 except OSError:
1369 if os.path.exists(cipd_cache_dir):
1370 raise
1371
1372 @contextlib.contextmanager
1373 def _create_ensure_file(self):
1374 try:
1375 ensure_file = None
1376 with tempfile.NamedTemporaryFile(
1377 suffix='.ensure', delete=False) as ensure_file:
1378 for subdir, packages in sorted(self._packages_by_subdir.iteritems()):
1379 ensure_file.write('@Subdir %s\n' % subdir)
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001380 for package in sorted(packages, key=lambda p: p.name):
John Budorick0f7b2002018-01-19 15:46:17 -08001381 ensure_file.write('%s %s\n' % (package.name, package.version))
1382 ensure_file.write('\n')
1383 yield ensure_file.name
1384 finally:
1385 if ensure_file is not None and os.path.exists(ensure_file.name):
1386 os.remove(ensure_file.name)
1387
1388 def ensure(self):
1389 """Run `cipd ensure`."""
1390 with self._mutator_lock:
1391 with self._create_ensure_file() as ensure_file:
1392 cmd = [
1393 'cipd', 'ensure',
1394 '-log-level', 'error',
1395 '-root', self.root_dir,
1396 '-ensure-file', ensure_file,
1397 ]
1398 gclient_utils.CheckCallAndFilterAndHeader(cmd)
1399
John Budorickd3ba72b2018-03-20 12:27:42 -07001400 def run(self, command):
1401 if command == 'update':
1402 self.ensure()
1403 elif command == 'revert':
1404 self.clobber()
1405 self.ensure()
1406
John Budorick0f7b2002018-01-19 15:46:17 -08001407 def created_package(self, package):
1408 """Checks whether this root created the given package.
1409
1410 Args:
1411 package: CipdPackage; the package to check.
1412 Returns:
1413 bool; whether this root created the given package.
1414 """
1415 return package in self._all_packages
1416
1417 @property
1418 def root_dir(self):
1419 return self._root_dir
1420
1421 @property
1422 def service_url(self):
1423 return self._service_url
1424
1425
1426class CipdWrapper(SCMWrapper):
1427 """Wrapper for CIPD.
1428
1429 Currently only supports chrome-infra-packages.appspot.com.
1430 """
John Budorick3929e9e2018-02-04 18:18:07 -08001431 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001432
1433 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1434 out_cb=None, root=None, package=None):
1435 super(CipdWrapper, self).__init__(
1436 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1437 out_cb=out_cb)
1438 assert root.created_package(package)
1439 self._package = package
1440 self._root = root
1441
1442 #override
1443 def GetCacheMirror(self):
1444 return None
1445
1446 #override
1447 def GetActualRemoteURL(self, options):
1448 return self._root.service_url
1449
1450 #override
1451 def DoesRemoteURLMatch(self, options):
1452 del options
1453 return True
1454
1455 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001456 """Does nothing.
1457
1458 CIPD packages should be reverted at the root by running
1459 `CipdRoot.run('revert')`.
1460 """
1461 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001462
1463 def diff(self, options, args, file_list):
1464 """CIPD has no notion of diffing."""
1465 pass
1466
1467 def pack(self, options, args, file_list):
1468 """CIPD has no notion of diffing."""
1469 pass
1470
1471 def revinfo(self, options, args, file_list):
1472 """Grab the instance ID."""
1473 try:
1474 tmpdir = tempfile.mkdtemp()
1475 describe_json_path = os.path.join(tmpdir, 'describe.json')
1476 cmd = [
1477 'cipd', 'describe',
1478 self._package.name,
1479 '-log-level', 'error',
1480 '-version', self._package.version,
1481 '-json-output', describe_json_path
1482 ]
1483 gclient_utils.CheckCallAndFilter(
1484 cmd, filter_fn=lambda _line: None, print_stdout=False)
1485 with open(describe_json_path) as f:
1486 describe_json = json.load(f)
1487 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1488 finally:
1489 gclient_utils.rmtree(tmpdir)
1490
1491 def status(self, options, args, file_list):
1492 pass
1493
1494 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001495 """Does nothing.
1496
1497 CIPD packages should be updated at the root by running
1498 `CipdRoot.run('update')`.
1499 """
1500 pass