blob: a322b99764cc7eca58a6c40ce5e576405a002b84 [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
Edward Lemurd328b472018-07-12 21:38:49 +0000306 def _FetchAndReset(self, ref, remote_ref, revision, file_list, options):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800307 """Equivalent to git fetch; git reset."""
Edward Lemurd328b472018-07-12 21:38:49 +0000308 self._UpdateBranchHeads(options, ref, remote_ref, fetch=False)
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)
Edward Lemurd328b472018-07-12 21:38:49 +0000311 self._Scrub(revision or remote_ref, 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 Lesmesc621b212018-03-21 20:26:56 -0400348 def apply_patch_ref(self, patch_repo, patch_ref, options, file_list):
349 base_rev = self._Capture(['rev-parse', 'HEAD'])
350 self.Print('===Applying patch ref===')
Andrii Shyshkalov690d8d42018-06-14 22:57:17 +0000351 self.Print('Repo is %r @ %r, ref is %r, root is %r' % (
352 patch_repo, patch_ref, base_rev, self.checkout_path))
Edward Lesmesc621b212018-03-21 20:26:56 -0400353 self._Capture(['reset', '--hard'])
354 self._Capture(['fetch', patch_repo, patch_ref])
Andrii Shyshkalov690d8d42018-06-14 22:57:17 +0000355 if file_list is not None:
356 file_list.extend(self._GetDiffFilenames('FETCH_HEAD'))
357 self._Capture(['checkout', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400358
Andrii Shyshkalov690d8d42018-06-14 22:57:17 +0000359 if options.rebase_patch_ref:
360 try:
361 # TODO(ehmaldonado): Look into cherry-picking to avoid an expensive
362 # checkout + rebase.
363 self._Capture(['rebase', base_rev])
364 except subprocess2.CalledProcessError as e:
365 self.Print('Failed to apply %r @ %r to %r at %r' % (
366 patch_repo, patch_ref, base_rev, self.checkout_path))
367 self.Print('git returned non-zero exit status %s:\n%s' % (
368 e.returncode, e.stderr))
369 self._Capture(['rebase', '--abort'])
370 raise
Edward Lesmesc621b212018-03-21 20:26:56 -0400371 if options.reset_patch_ref:
372 self._Capture(['reset', '--soft', base_rev])
373
msb@chromium.orge28e4982009-09-25 20:51:45 +0000374 def update(self, options, args, file_list):
375 """Runs git to update or transparently checkout the working copy.
376
377 All updated files will be appended to file_list.
378
379 Raises:
380 Error: if can't get URL for relative path.
381 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000382 if args:
383 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
384
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000385 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000386
Edward Lemurd328b472018-07-12 21:38:49 +0000387 # If a dependency is not pinned, track refs/heads/master by default.
388 default_rev = 'refs/heads/master'
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000389 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000390 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000391 managed = True
Edward Lemurd328b472018-07-12 21:38:49 +0000392
msb@chromium.orge28e4982009-09-25 20:51:45 +0000393 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000394 # Override the revision number.
395 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000396 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000397 # Check again for a revision in case an initial ref was specified
398 # in the url, for example bla.git@refs/heads/custombranch
399 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000400 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000401 if not revision:
402 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000403
szager@chromium.org8a139702014-06-20 15:55:01 +0000404 if managed:
405 self._DisableHooks()
406
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000407 printed_path = False
408 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000409 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700410 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000411 verbose = ['--verbose']
412 printed_path = True
413
Edward Lemurd328b472018-07-12 21:38:49 +0000414 ref = remote_ref = None
415 # Support the 'branch:revision' syntax.
416 if ':' in revision:
417 ref, revision = revision.split(':')
418 if not gclient_utils.IsFullGitSha(revision):
419 raise gclient_utils.Error(
420 'Invalid format: %s:%s. revision must be a git hash.' % (
421 remote_ref, revision))
422 elif not gclient_utils.IsFullGitSha(revision):
423 ref = revision
424 revision = None
smut@google.comd33eab32014-07-07 19:35:18 +0000425
Edward Lemurd328b472018-07-12 21:38:49 +0000426 if ref:
427 if ref.startswith('origin/'):
428 ref = ref[len('origin/'):]
429 if not ref.startswith('refs/'):
430 ref = 'refs/heads/' + ref
431 remote_ref = scm.GIT.RefToRemoteRef(ref, self.remote)
432 if remote_ref:
433 # If there is a corresponding remote ref for |ref|, RefToRemoteRef
434 # returns a tuple, so we need to join it to get the actual remote ref.
435 # E.g. ('refs/remotes/origin/', 'branch-name')
436 # -> 'refs/remotes/origin/branch-name
437 remote_ref = ''.join(remote_ref)
438 else:
439 # Otherwise, it returns None, so we use |ref|.
440 remote_ref = ref
441
442 # If we're using a mirror, make sure it contains the ref we are asked to
443 # sync.
444 mirror = self._GetMirror(url, options, ref)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000445 if mirror:
446 url = mirror.mirror_path
447
primiano@chromium.org1c127382015-02-17 11:15:40 +0000448 # If we are going to introduce a new project, there is a possibility that
449 # we are syncing back to a state where the project was originally a
450 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
451 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
452 # In such case, we might have a backup of the former .git folder, which can
453 # be used to avoid re-fetching the entire repo again (useful for bisects).
454 backup_dir = self.GetGitBackupDirPath()
455 target_dir = os.path.join(self.checkout_path, '.git')
456 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
457 gclient_utils.safe_makedirs(self.checkout_path)
458 os.rename(backup_dir, target_dir)
459 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800460 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000461
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000462 if (not os.path.exists(self.checkout_path) or
463 (os.path.isdir(self.checkout_path) and
464 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000465 if mirror:
Edward Lemurd328b472018-07-12 21:38:49 +0000466 self._UpdateMirrorIfNotContains(mirror, options, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000467 try:
Edward Lemurd328b472018-07-12 21:38:49 +0000468 self._Clone(ref, remote_ref, revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000469 except subprocess2.CalledProcessError:
470 self._DeleteOrMove(options.force)
Edward Lemurd328b472018-07-12 21:38:49 +0000471 self._Clone(ref, remote_ref, revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000472 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800473 files = self._Capture(
474 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000475 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000476 if not verbose:
477 # Make the output a little prettier. It's nice to have some whitespace
478 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000479 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000480 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000481
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000482 if not managed:
Edward Lemurd328b472018-07-12 21:38:49 +0000483 self._UpdateBranchHeads(options, ref, remote_ref, fetch=False)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000484 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
485 return self._Capture(['rev-parse', '--verify', 'HEAD'])
486
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000487 self._maybe_break_locks(options)
488
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000489 if mirror:
Edward Lemurd328b472018-07-12 21:38:49 +0000490 self._UpdateMirrorIfNotContains(mirror, options, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000491
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000492 # See if the url has changed (the unittests use git://foo for the url, let
493 # that through).
494 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
495 return_early = False
496 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
497 # unit test pass. (and update the comment above)
498 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
499 # This allows devs to use experimental repos which have a different url
500 # but whose branch(s) are the same as official repos.
borenet@google.comb09097a2014-04-09 19:09:08 +0000501 if (current_url.rstrip('/') != url.rstrip('/') and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000502 url != 'git://foo' and
503 subprocess2.capture(
504 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
505 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000506 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000507 if not (options.force or options.reset):
508 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700509 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000510 # Switch over to the new upstream
511 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000512 if mirror:
513 with open(os.path.join(
514 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
515 'w') as fh:
516 fh.write(os.path.join(url, 'objects'))
Edward Lemurd328b472018-07-12 21:38:49 +0000517 self._EnsureValidHeadObjectOrCheckout(ref, remote_ref, revision, options,
518 url)
519 self._FetchAndReset(ref, remote_ref, revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000520
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000521 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000522 else:
Edward Lemurd328b472018-07-12 21:38:49 +0000523 self._EnsureValidHeadObjectOrCheckout(ref, remote_ref, revision, options,
524 url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000525
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000526 if return_early:
527 return self._Capture(['rev-parse', '--verify', 'HEAD'])
528
msb@chromium.org5bde4852009-12-14 16:47:12 +0000529 cur_branch = self._GetCurrentBranch()
530
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000531 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000532 # 0) HEAD is detached. Probably from our initial clone.
533 # - make sure HEAD is contained by a named ref, then update.
534 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700535 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000536 # - try to rebase onto the new hash or branch
537 # 2) current branch is tracking a remote branch with local committed
538 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000539 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000540 # 3) current branch is tracking a remote branch w/or w/out changes, and
541 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000542 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000543 # 4) current branch is tracking a remote branch, but DEPS switches to a
544 # different remote branch, and
545 # a) current branch has no local changes, and --force:
546 # - checkout new branch
547 # b) current branch has local changes, and --force and --reset:
548 # - checkout new branch
549 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000550
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000551 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
552 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000553 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
554 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000555 if cur_branch is None:
556 upstream_branch = None
557 current_type = "detached"
558 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000559 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000560 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
561 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
562 current_type = "hash"
563 logging.debug("Current branch is not tracking an upstream (remote)"
564 " branch.")
565 elif upstream_branch.startswith('refs/remotes'):
566 current_type = "branch"
567 else:
568 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000569
Edward Lemurd328b472018-07-12 21:38:49 +0000570 if revision and not scm.GIT.IsValidRevision(
571 self.checkout_path, revision, sha_only=True):
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000572 # Update the remotes first so we have all the refs.
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000573 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000574 cwd=self.checkout_path)
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000575 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000576 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000577
Edward Lemurd328b472018-07-12 21:38:49 +0000578 self._UpdateBranchHeads(options, ref, remote_ref, fetch=True)
mmoss@chromium.orge409df62013-04-16 17:28:57 +0000579
Edward Lemurd328b472018-07-12 21:38:49 +0000580 revision = self._AutoFetchRevision(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200581
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000582 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000583 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000584 target = 'HEAD'
585 if options.upstream and upstream_branch:
586 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800587 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000588
msb@chromium.org786fb682010-06-02 15:16:23 +0000589 if current_type == 'detached':
590 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800591 # We just did a Scrub, this is as clean as it's going to get. In
592 # particular if HEAD is a commit that contains two versions of the same
593 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
594 # to actually "Clean" the checkout; that commit is uncheckoutable on this
595 # system. The best we can do is carry forward to the checkout step.
596 if not (options.force or options.reset):
Edward Lemurd328b472018-07-12 21:38:49 +0000597 self._CheckClean(revision or ref)
598 self._CheckDetachedHead(revision or ref, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000599 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000600 self.Print('Up-to-date; skipping checkout.')
601 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000602 # 'git checkout' may need to overwrite existing untracked files. Allow
603 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000604 self._Checkout(
605 options,
Edward Lemurd328b472018-07-12 21:38:49 +0000606 revision or ref,
smut@google.com34b4e982016-05-16 19:06:07 +0000607 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000608 quiet=True,
609 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000610 if not printed_path:
Edward Lemurd328b472018-07-12 21:38:49 +0000611 self.Print('_____ %s at %s' % (self.relpath, revision or ref),
612 timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000613 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000614 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700615 # Can't find a merge-base since we don't know our upstream. That makes
616 # this command VERY likely to produce a rebase failure. For now we
617 # assume origin is our upstream since that's what the old behavior was.
Edward Lemurd328b472018-07-12 21:38:49 +0000618 upstream_branch = revision or ref or self.remote
agable1a8439a2016-10-24 16:36:14 -0700619 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700620 printed_path=printed_path, merge=options.merge)
621 printed_path = True
Edward Lemurd328b472018-07-12 21:38:49 +0000622 elif remote_ref and remote_ref != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000623 # case 4
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000624 if not printed_path:
Edward Lemurd328b472018-07-12 21:38:49 +0000625 self.Print('_____ %s at %s' % (self.relpath, ref), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000626 switch_error = ("Could not switch upstream branch from %s to %s\n"
Edward Lemurd328b472018-07-12 21:38:49 +0000627 % (upstream_branch, ref) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000628 "Please use --force or merge or rebase manually:\n" +
Edward Lemurd328b472018-07-12 21:38:49 +0000629 "cd %s; git rebase %s\n" % (self.checkout_path, ref) +
630 "OR git checkout -b <some new branch> %s" % ref)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000631 force_switch = False
632 if options.force:
633 try:
Edward Lemurd328b472018-07-12 21:38:49 +0000634 self._CheckClean(ref)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000635 # case 4a
636 force_switch = True
637 except gclient_utils.Error as e:
638 if options.reset:
639 # case 4b
640 force_switch = True
641 else:
642 switch_error = '%s\n%s' % (e.message, switch_error)
643 if force_switch:
644 self.Print("Switching upstream branch from %s to %s" %
Edward Lemurd328b472018-07-12 21:38:49 +0000645 (upstream_branch, ref))
646 switch_branch = 'gclient_' + re.sub('[^A-Za-z0-9]', '_', ref)
647 self._Capture(['branch', '-f', switch_branch, ref])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000648 self._Checkout(options, switch_branch, force=True, quiet=True)
Edward Lemurd328b472018-07-12 21:38:49 +0000649 if revision:
650 self._Scrub(revision, options)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000651 else:
652 # case 4c
653 raise gclient_utils.Error(switch_error)
Edward Lemurd328b472018-07-12 21:38:49 +0000654 elif revision:
655 # case 2
656 self._AttemptRebase(upstream_branch, file_list, options,
657 newbase=revision, printed_path=printed_path,
658 merge=options.merge)
659 printed_path = True
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000660 else:
661 # case 3 - the default case
Edward Lemurd328b472018-07-12 21:38:49 +0000662 # The same ref as |upstream_branch| was specified, and no revision was
663 # used.
Aaron Gablef4068aa2017-12-12 15:14:09 -0800664 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000665 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000666 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000667 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000668 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000669 if options.merge:
670 merge_args.append('--ff')
671 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000672 merge_args.append('--ff-only')
673 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000674 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000675 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700676 rebase_files = []
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000677 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
678 if not printed_path:
Edward Lemurd328b472018-07-12 21:38:49 +0000679 self.Print('_____ %s at %s' % (self.relpath, ref),
agable83faed02016-10-24 14:37:10 -0700680 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000681 printed_path = True
682 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000683 if not options.auto_rebase:
684 try:
685 action = self._AskForData(
686 'Cannot %s, attempt to rebase? '
687 '(y)es / (q)uit / (s)kip : ' %
688 ('merge' if options.merge else 'fast-forward merge'),
689 options)
690 except ValueError:
691 raise gclient_utils.Error('Invalid Character')
692 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700693 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000694 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000695 printed_path = True
696 break
697 elif re.match(r'quit|q', action, re.I):
698 raise gclient_utils.Error("Can't fast-forward, please merge or "
699 "rebase manually.\n"
700 "cd %s && git " % self.checkout_path
701 + "rebase %s" % upstream_branch)
702 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000703 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000704 return
705 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000706 self.Print('Input not recognized')
smut@google.com27c9c8a2014-09-11 19:57:55 +0000707 elif re.match("error: Your local changes to '.*' would be "
708 "overwritten by merge. Aborting.\nPlease, commit your "
709 "changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000710 e.stderr):
711 if not printed_path:
Edward Lemurd328b472018-07-12 21:38:49 +0000712 self.Print('_____ %s at %s' % (self.relpath, ref),
agable83faed02016-10-24 14:37:10 -0700713 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000714 printed_path = True
715 raise gclient_utils.Error(e.stderr)
716 else:
717 # Some other problem happened with the merge
718 logging.error("Error during fast-forward merge in %s!" % self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000719 self.Print(e.stderr)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000720 raise
721 else:
722 # Fast-forward merge was successful
723 if not re.match('Already up-to-date.', merge_output) or verbose:
724 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700725 self.Print('_____ %s at %s' % (self.relpath, revision),
726 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000727 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000728 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000729 if not verbose:
730 # Make the output a little prettier. It's nice to have some
731 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000732 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000733
agablec3937b92016-10-25 10:13:03 -0700734 if file_list is not None:
735 file_list.extend(
736 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000737
738 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000739 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700740 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000741 '\nConflict while rebasing this branch.\n'
742 'Fix the conflict and run gclient again.\n'
743 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700744 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000745
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000746 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000747 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
748 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000749
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000750 # If --reset and --delete_unversioned_trees are specified, remove any
751 # untracked directories.
752 if options.reset and options.delete_unversioned_trees:
753 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
754 # merge-base by default), so doesn't include untracked files. So we use
755 # 'git ls-files --directory --others --exclude-standard' here directly.
756 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800757 ['-c', 'core.quotePath=false', 'ls-files',
758 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000759 self.checkout_path)
760 for path in (p for p in paths.splitlines() if p.endswith('/')):
761 full_path = os.path.join(self.checkout_path, path)
762 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000763 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000764 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000765
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000766 return self._Capture(['rev-parse', '--verify', 'HEAD'])
767
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000768 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000769 """Reverts local modifications.
770
771 All reverted files will be appended to file_list.
772 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000773 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000774 # revert won't work if the directory doesn't exist. It needs to
775 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000776 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000777 # Don't reuse the args.
778 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000779
780 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000781 if options.upstream:
782 if self._GetCurrentBranch():
783 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
784 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000785 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000786 if not deps_revision:
787 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000788 if deps_revision.startswith('refs/heads/'):
789 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700790 try:
791 deps_revision = self.GetUsableRev(deps_revision, options)
792 except NoUsableRevError as e:
793 # If the DEPS entry's url and hash changed, try to update the origin.
794 # See also http://crbug.com/520067.
795 logging.warn(
796 'Couldn\'t find usable revision, will retrying to update instead: %s',
797 e.message)
798 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000799
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000800 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800801 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000802
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800803 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000804 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000805
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000806 if file_list is not None:
807 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
808
809 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000810 """Returns revision"""
811 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000812
msb@chromium.orge28e4982009-09-25 20:51:45 +0000813 def runhooks(self, options, args, file_list):
814 self.status(options, args, file_list)
815
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000816 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000817 """Display status information."""
818 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000819 self.Print('________ couldn\'t run status in %s:\n'
820 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000821 else:
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700822 try:
823 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
824 except subprocess2.CalledProcessError:
825 merge_base = []
Aaron Gablef4068aa2017-12-12 15:14:09 -0800826 self._Run(
827 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
828 options, stdout=self.out_fh, always=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000829 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800830 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000831 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000832
smutae7ea312016-07-18 11:59:41 -0700833 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700834 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700835 sha1 = None
836 if not os.path.isdir(self.checkout_path):
837 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800838 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700839
840 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
841 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700842 else:
agable41e3a6c2016-10-20 11:36:56 -0700843 # May exist in origin, but we don't have it yet, so fetch and look
844 # again.
845 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700846 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
847 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700848
849 if not sha1:
850 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800851 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700852
853 return sha1
854
primiano@chromium.org1c127382015-02-17 11:15:40 +0000855 def GetGitBackupDirPath(self):
856 """Returns the path where the .git folder for the current project can be
857 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
858 return os.path.join(self._root_dir,
859 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
860
Edward Lemurd328b472018-07-12 21:38:49 +0000861 def _GetMirror(self, url, options, ref=None):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000862 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000863 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000864 return None
Edward Lemurd328b472018-07-12 21:38:49 +0000865 # Don't try to fetch local refs in the mirror.
866 if ref and ref.startswith('refs/remotes'):
867 ref = None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000868 mirror_kwargs = {
869 'print_func': self.filter,
Edward Lemurd328b472018-07-12 21:38:49 +0000870 'refs': [ref] if ref else [],
hinoka@google.comb1b54572014-04-16 22:29:23 +0000871 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000872 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
873 mirror_kwargs['refs'].append('refs/branch-heads/*')
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000874 if hasattr(options, 'with_tags') and options.with_tags:
875 mirror_kwargs['refs'].append('refs/tags/*')
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000876 return git_cache.Mirror(url, **mirror_kwargs)
877
Edward Lemurd328b472018-07-12 21:38:49 +0000878 def _UpdateMirrorIfNotContains(self, mirror, options, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800879 """Update a git mirror by fetching the latest commits from the remote,
880 unless mirror already contains revision whose type is sha1 hash.
881 """
Edward Lemurd328b472018-07-12 21:38:49 +0000882 if revision and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800883 if options.verbose:
884 self.Print('skipping mirror update, it has rev=%s already' % revision,
885 timestamp=False)
886 return
887
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000888 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000889 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000890 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000891 depth = 10
892 else:
893 depth = 10000
894 else:
895 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000896 mirror.populate(verbose=options.verbose,
897 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000898 depth=depth,
899 ignore_lock=getattr(options, 'ignore_locks', False),
900 lock_timeout=getattr(options, 'lock_timeout', 0))
901 mirror.unlock()
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000902
Edward Lemurd328b472018-07-12 21:38:49 +0000903 def _Clone(self, ref, remote_ref, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000904 """Clone a git repository from the given URL.
905
msb@chromium.org786fb682010-06-02 15:16:23 +0000906 Once we've cloned the repo, we checkout a working branch if the specified
907 revision is a branch head. If it is a tag or a specific commit, then we
908 leave HEAD detached as it makes future updates simpler -- in this case the
909 user should first create a new branch or switch to an existing branch before
910 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000911 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000912 # git clone doesn't seem to insert a newline properly before printing
913 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000914 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +0000915 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +0000916 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000917 if self.cache_dir:
918 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000919 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000920 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000921 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +0000922 # If the parent directory does not exist, Git clone on Windows will not
923 # create it, so we need to do it manually.
924 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000925 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000926
927 template_dir = None
928 if hasattr(options, 'no_history') and options.no_history:
Edward Lemurd328b472018-07-12 21:38:49 +0000929 if revision and gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000930 # In the case of a subproject, the pinned sha is not necessarily the
931 # head of the remote branch (so we can't just use --depth=N). Instead,
932 # we tell git to fetch all the remote objects from SHA..HEAD by means of
933 # a template git dir which has a 'shallow' file pointing to the sha.
934 template_dir = tempfile.mkdtemp(
935 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
936 dir=parent_dir)
937 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
938 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
939 template_file.write(revision)
940 clone_cmd.append('--template=' + template_dir)
941 else:
942 # Otherwise, we're just interested in the HEAD. Just use --depth.
943 clone_cmd.append('--depth=1')
944
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000945 tmp_dir = tempfile.mkdtemp(
946 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
947 dir=parent_dir)
948 try:
949 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +0100950 if self.print_outbuf:
951 print_stdout = True
952 stdout = gclient_utils.WriteToStdout(self.out_fh)
953 else:
954 print_stdout = False
955 stdout = self.out_fh
956 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
957 print_stdout=print_stdout, stdout=stdout)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000958 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000959 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
960 os.path.join(self.checkout_path, '.git'))
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000961 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000962 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000963 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000964 finally:
965 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000966 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000967 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000968 if template_dir:
969 gclient_utils.rmtree(template_dir)
Edward Lemurd328b472018-07-12 21:38:49 +0000970 self._UpdateBranchHeads(options, ref, remote_ref, fetch=True)
971 if revision:
972 revision = self._AutoFetchRevision(options, revision)
973 self._Checkout(options, revision or remote_ref, quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000974 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +0000975 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000976 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +0000977 ('Checked out %s to a detached HEAD. Before making any commits\n'
978 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
979 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
Edward Lemurd328b472018-07-12 21:38:49 +0000980 'create a new branch for your work.') % (
981 revision or remote_ref, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000982
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000983 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000984 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000985 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000986 raise gclient_utils.Error("Background task requires input. Rerun "
987 "gclient with --jobs=1 so that\n"
988 "interaction is possible.")
989 try:
990 return raw_input(prompt)
991 except KeyboardInterrupt:
992 # Hide the exception.
993 sys.exit(1)
994
995
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000996 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000997 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000998 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000999 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001000 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001001 revision = upstream
1002 if newbase:
1003 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001004 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001005 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001006 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001007 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001008 printed_path = True
1009 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001010 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001011
1012 if merge:
1013 merge_output = self._Capture(['merge', revision])
1014 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001015 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001016 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001017
1018 # Build the rebase command here using the args
1019 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1020 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001021 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001022 rebase_cmd.append('--verbose')
1023 if newbase:
1024 rebase_cmd.extend(['--onto', newbase])
1025 rebase_cmd.append(upstream)
1026 if branch:
1027 rebase_cmd.append(branch)
1028
1029 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001030 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001031 except subprocess2.CalledProcessError, e:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001032 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
1033 re.match(r'cannot rebase: your index contains uncommitted changes',
1034 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001035 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001036 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001037 'Cannot rebase because of unstaged changes.\n'
1038 '\'git reset --hard HEAD\' ?\n'
1039 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001040 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001041 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001042 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001043 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001044 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001045 break
1046 elif re.match(r'quit|q', rebase_action, re.I):
1047 raise gclient_utils.Error("Please merge or rebase manually\n"
1048 "cd %s && git " % self.checkout_path
1049 + "%s" % ' '.join(rebase_cmd))
1050 elif re.match(r'show|s', rebase_action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001051 self.Print('%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001052 continue
1053 else:
1054 gclient_utils.Error("Input not recognized")
1055 continue
1056 elif re.search(r'^CONFLICT', e.stdout, re.M):
1057 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1058 "Fix the conflict and run gclient again.\n"
1059 "See 'man git-rebase' for details.\n")
1060 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001061 self.Print(e.stdout.strip())
1062 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001063 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1064 "manually.\ncd %s && git " %
1065 self.checkout_path
1066 + "%s" % ' '.join(rebase_cmd))
1067
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001068 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001069 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001070 # Make the output a little prettier. It's nice to have some
1071 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001072 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001073
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001074 @staticmethod
1075 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001076 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1077 if not ok:
1078 raise gclient_utils.Error('git version %s < minimum required %s' %
1079 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001080
Edward Lemurd328b472018-07-12 21:38:49 +00001081 def _EnsureValidHeadObjectOrCheckout(self, ref, remote_ref, revision, options,
1082 url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001083 # Special case handling if all 3 conditions are met:
1084 # * the mirros have recently changed, but deps destination remains same,
1085 # * the git histories of mirrors are conflicting.
1086 # * git cache is used
1087 # This manifests itself in current checkout having invalid HEAD commit on
1088 # most git operations. Since git cache is used, just deleted the .git
1089 # folder, and re-create it by cloning.
1090 try:
1091 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1092 except subprocess2.CalledProcessError as e:
1093 if ('fatal: bad object HEAD' in e.stderr
1094 and self.cache_dir and self.cache_dir in url):
1095 self.Print((
1096 'Likely due to DEPS change with git cache_dir, '
1097 'the current commit points to no longer existing object.\n'
1098 '%s' % e)
1099 )
1100 self._DeleteOrMove(options.force)
Edward Lemurd328b472018-07-12 21:38:49 +00001101 self._Clone(ref, remote_ref, revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001102 else:
1103 raise
1104
msb@chromium.org786fb682010-06-02 15:16:23 +00001105 def _IsRebasing(self):
1106 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1107 # have a plumbing command to determine whether a rebase is in progress, so
1108 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1109 g = os.path.join(self.checkout_path, '.git')
1110 return (
1111 os.path.isdir(os.path.join(g, "rebase-merge")) or
1112 os.path.isdir(os.path.join(g, "rebase-apply")))
1113
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001114 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001115 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1116 if os.path.exists(lockfile):
1117 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001118 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001119 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1120 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001121 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001122
msb@chromium.org786fb682010-06-02 15:16:23 +00001123 # Make sure the tree is clean; see git-rebase.sh for reference
1124 try:
1125 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001126 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001127 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001128 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001129 '\tYou have unstaged changes.\n'
1130 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001131 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001132 try:
1133 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001134 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001135 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001136 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001137 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001138 '\tYour index contains uncommitted changes\n'
1139 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001140 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001141
agable83faed02016-10-24 14:37:10 -07001142 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001143 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1144 # reference by a commit). If not, error out -- most likely a rebase is
1145 # in progress, try to detect so we can give a better error.
1146 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001147 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1148 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001149 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001150 # Commit is not contained by any rev. See if the user is rebasing:
1151 if self._IsRebasing():
1152 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001153 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001154 '\tAlready in a conflict, i.e. (no branch).\n'
1155 '\tFix the conflict and run gclient again.\n'
1156 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1157 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001158 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001159 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001160 name = ('saved-by-gclient-' +
1161 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001162 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001163 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001164 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001165
msb@chromium.org5bde4852009-12-14 16:47:12 +00001166 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001167 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001168 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001169 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001170 return None
1171 return branch
1172
borenet@google.comc3e09d22014-04-10 13:58:18 +00001173 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001174 kwargs.setdefault('cwd', self.checkout_path)
1175 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001176 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001177 env = scm.GIT.ApplyEnvVars(kwargs)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001178 ret = subprocess2.check_output(['git'] + args, env=env, **kwargs)
1179 if strip:
1180 ret = ret.strip()
1181 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001182
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001183 def _Checkout(self, options, ref, force=False, quiet=None):
1184 """Performs a 'git-checkout' operation.
1185
1186 Args:
1187 options: The configured option set
1188 ref: (str) The branch/commit to checkout
1189 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1190 'None', the behavior is inferred from 'options.verbose'.
1191 Returns: (str) The output of the checkout operation
1192 """
1193 if quiet is None:
1194 quiet = (not options.verbose)
1195 checkout_args = ['checkout']
1196 if force:
1197 checkout_args.append('--force')
1198 if quiet:
1199 checkout_args.append('--quiet')
1200 checkout_args.append(ref)
Edward Lemurd328b472018-07-12 21:38:49 +00001201 checkout_args.append('--')
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001202 return self._Capture(checkout_args)
1203
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001204 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1205 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001206 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemurd64781e2018-07-11 23:09:55 +00001207 # When a mirror is configured, it fetches only the refs/heads, and possibly
1208 # the refs/branch-heads and refs/tags, but not the refs/changes. So, if
1209 # we're asked to fetch a refs/changes ref from the mirror, it won't have it.
1210 # This makes sure that we always fetch refs/changes directly from the
1211 # repository and not from the mirror.
1212 if refspec and refspec.startswith('refs/changes'):
1213 remote, _ = gclient_utils.SplitUrlRevision(self.url)
1214 # Make sure that we fetch the (remote) refs/changes/xx ref to the (local)
1215 # refs/changes/xx ref.
1216 if ':' not in refspec:
1217 refspec += ':' + refspec
dnj@chromium.org680f2172014-06-25 00:39:32 +00001218 fetch_cmd = cfg + [
1219 'fetch',
1220 remote or self.remote,
1221 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001222 if refspec:
1223 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001224
1225 if prune:
1226 fetch_cmd.append('--prune')
1227 if options.verbose:
1228 fetch_cmd.append('--verbose')
1229 elif quiet:
1230 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001231 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001232
1233 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1234 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1235
Edward Lemurd328b472018-07-12 21:38:49 +00001236 def _UpdateBranchHeads(self, options, ref, remote_ref, fetch=False):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001237 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1238 if requested."""
1239 need_fetch = fetch
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001240 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001241 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001242 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1243 '^\\+refs/branch-heads/\\*:.*$']
1244 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001245 need_fetch = True
1246 if hasattr(options, 'with_tags') and options.with_tags:
1247 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1248 '+refs/tags/*:refs/tags/*',
1249 '^\\+refs/tags/\\*:.*$']
1250 self._Run(config_cmd, options)
1251 need_fetch = True
Edward Lemurd328b472018-07-12 21:38:49 +00001252 # Make sure we fetch the ref we're asked to sync, if any.
1253 if ref and not ref.startswith(('refs/remotes',)):
1254 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1255 '+%s:%s' % (ref, remote_ref), '--add']
1256 self._Run(config_cmd, options)
1257 need_fetch = True
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001258 if fetch and need_fetch:
bpastene2a3e9912016-09-07 16:22:25 -07001259 self._Fetch(options, prune=options.force)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001260
Edward Lemurd328b472018-07-12 21:38:49 +00001261 def _AutoFetchRevision(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001262 """Attempts to fetch |revision| if not available in local repo.
1263
1264 Returns possibly updated revision."""
Edward Lemurd328b472018-07-12 21:38:49 +00001265 if revision and not scm.GIT.IsValidRevision(self.checkout_path, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001266 self._Fetch(options, refspec=revision)
1267 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1268 return revision
1269
dnj@chromium.org680f2172014-06-25 00:39:32 +00001270 def _Run(self, args, options, show_header=True, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001271 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001272 kwargs.setdefault('cwd', self.checkout_path)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001273 kwargs.setdefault('stdout', self.out_fh)
szager@chromium.org848fd492014-04-09 19:06:44 +00001274 kwargs['filter_fn'] = self.filter
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001275 kwargs.setdefault('print_stdout', False)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001276 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001277 cmd = ['git'] + args
dnj@chromium.org680f2172014-06-25 00:39:32 +00001278 if show_header:
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001279 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1280 else:
1281 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001282
1283
1284class CipdPackage(object):
1285 """A representation of a single CIPD package."""
1286
John Budorickd3ba72b2018-03-20 12:27:42 -07001287 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001288 self._authority_for_subdir = authority_for_subdir
1289 self._name = name
1290 self._version = version
1291
1292 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001293 def authority_for_subdir(self):
1294 """Whether this package has authority to act on behalf of its subdir.
1295
1296 Some operations should only be performed once per subdirectory. A package
1297 that has authority for its subdirectory is the only package that should
1298 perform such operations.
1299
1300 Returns:
1301 bool; whether this package has subdir authority.
1302 """
1303 return self._authority_for_subdir
1304
1305 @property
1306 def name(self):
1307 return self._name
1308
1309 @property
1310 def version(self):
1311 return self._version
1312
1313
1314class CipdRoot(object):
1315 """A representation of a single CIPD root."""
1316 def __init__(self, root_dir, service_url):
1317 self._all_packages = set()
1318 self._mutator_lock = threading.Lock()
1319 self._packages_by_subdir = collections.defaultdict(list)
1320 self._root_dir = root_dir
1321 self._service_url = service_url
1322
1323 def add_package(self, subdir, package, version):
1324 """Adds a package to this CIPD root.
1325
1326 As far as clients are concerned, this grants both root and subdir authority
1327 to packages arbitrarily. (The implementation grants root authority to the
1328 first package added and subdir authority to the first package added for that
1329 subdir, but clients should not depend on or expect that behavior.)
1330
1331 Args:
1332 subdir: str; relative path to where the package should be installed from
1333 the cipd root directory.
1334 package: str; the cipd package name.
1335 version: str; the cipd package version.
1336 Returns:
1337 CipdPackage; the package that was created and added to this root.
1338 """
1339 with self._mutator_lock:
1340 cipd_package = CipdPackage(
1341 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001342 not self._packages_by_subdir[subdir])
1343 self._all_packages.add(cipd_package)
1344 self._packages_by_subdir[subdir].append(cipd_package)
1345 return cipd_package
1346
1347 def packages(self, subdir):
1348 """Get the list of configured packages for the given subdir."""
1349 return list(self._packages_by_subdir[subdir])
1350
1351 def clobber(self):
1352 """Remove the .cipd directory.
1353
1354 This is useful for forcing ensure to redownload and reinitialize all
1355 packages.
1356 """
1357 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001358 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001359 try:
1360 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1361 except OSError:
1362 if os.path.exists(cipd_cache_dir):
1363 raise
1364
1365 @contextlib.contextmanager
1366 def _create_ensure_file(self):
1367 try:
1368 ensure_file = None
1369 with tempfile.NamedTemporaryFile(
1370 suffix='.ensure', delete=False) as ensure_file:
1371 for subdir, packages in sorted(self._packages_by_subdir.iteritems()):
1372 ensure_file.write('@Subdir %s\n' % subdir)
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001373 for package in sorted(packages, key=lambda p: p.name):
John Budorick0f7b2002018-01-19 15:46:17 -08001374 ensure_file.write('%s %s\n' % (package.name, package.version))
1375 ensure_file.write('\n')
1376 yield ensure_file.name
1377 finally:
1378 if ensure_file is not None and os.path.exists(ensure_file.name):
1379 os.remove(ensure_file.name)
1380
1381 def ensure(self):
1382 """Run `cipd ensure`."""
1383 with self._mutator_lock:
1384 with self._create_ensure_file() as ensure_file:
1385 cmd = [
1386 'cipd', 'ensure',
1387 '-log-level', 'error',
1388 '-root', self.root_dir,
1389 '-ensure-file', ensure_file,
1390 ]
1391 gclient_utils.CheckCallAndFilterAndHeader(cmd)
1392
John Budorickd3ba72b2018-03-20 12:27:42 -07001393 def run(self, command):
1394 if command == 'update':
1395 self.ensure()
1396 elif command == 'revert':
1397 self.clobber()
1398 self.ensure()
1399
John Budorick0f7b2002018-01-19 15:46:17 -08001400 def created_package(self, package):
1401 """Checks whether this root created the given package.
1402
1403 Args:
1404 package: CipdPackage; the package to check.
1405 Returns:
1406 bool; whether this root created the given package.
1407 """
1408 return package in self._all_packages
1409
1410 @property
1411 def root_dir(self):
1412 return self._root_dir
1413
1414 @property
1415 def service_url(self):
1416 return self._service_url
1417
1418
1419class CipdWrapper(SCMWrapper):
1420 """Wrapper for CIPD.
1421
1422 Currently only supports chrome-infra-packages.appspot.com.
1423 """
John Budorick3929e9e2018-02-04 18:18:07 -08001424 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001425
1426 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1427 out_cb=None, root=None, package=None):
1428 super(CipdWrapper, self).__init__(
1429 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1430 out_cb=out_cb)
1431 assert root.created_package(package)
1432 self._package = package
1433 self._root = root
1434
1435 #override
1436 def GetCacheMirror(self):
1437 return None
1438
1439 #override
1440 def GetActualRemoteURL(self, options):
1441 return self._root.service_url
1442
1443 #override
1444 def DoesRemoteURLMatch(self, options):
1445 del options
1446 return True
1447
1448 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001449 """Does nothing.
1450
1451 CIPD packages should be reverted at the root by running
1452 `CipdRoot.run('revert')`.
1453 """
1454 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001455
1456 def diff(self, options, args, file_list):
1457 """CIPD has no notion of diffing."""
1458 pass
1459
1460 def pack(self, options, args, file_list):
1461 """CIPD has no notion of diffing."""
1462 pass
1463
1464 def revinfo(self, options, args, file_list):
1465 """Grab the instance ID."""
1466 try:
1467 tmpdir = tempfile.mkdtemp()
1468 describe_json_path = os.path.join(tmpdir, 'describe.json')
1469 cmd = [
1470 'cipd', 'describe',
1471 self._package.name,
1472 '-log-level', 'error',
1473 '-version', self._package.version,
1474 '-json-output', describe_json_path
1475 ]
1476 gclient_utils.CheckCallAndFilter(
1477 cmd, filter_fn=lambda _line: None, print_stdout=False)
1478 with open(describe_json_path) as f:
1479 describe_json = json.load(f)
1480 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1481 finally:
1482 gclient_utils.rmtree(tmpdir)
1483
1484 def status(self, options, args, file_list):
1485 pass
1486
1487 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001488 """Does nothing.
1489
1490 CIPD packages should be updated at the root by running
1491 `CipdRoot.run('update')`.
1492 """
1493 pass