blob: f1a786253dc212a700d6967092429e4fa886ff75 [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 Lemurf9afc772018-07-18 22:40:32 +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 Lemurf9afc772018-07-18 22:40:32 +0000369 branch_rev = self._GetBranchForCommit(base_rev)
Edward Lesmesc621b212018-03-21 20:26:56 -0400370 self.Print('===Applying patch ref===')
Edward Lemurf9afc772018-07-18 22:40:32 +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 Lemurf9afc772018-07-18 22:40:32 +0000375 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400376
Edward Lemurf9afc772018-07-18 22:40:32 +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 # If a change was uploaded on top of another change, which has already
392 # landed, one of the commits in the cherry-pick range will be
393 # redundant, since it has already landed and its changes incorporated
394 # in the tree.
395 # We pass '--keep-redundant-commits' to ignore those changes.
396 self._Capture(['cherry-pick', merge_base + '..' + patch_rev,
397 '--keep-redundant-commits'])
398
399 if file_list is not None:
400 file_list.extend(self._GetDiffFilenames(base_rev))
401
402 except subprocess2.CalledProcessError as e:
403 self.Print('Failed to apply %r @ %r to %r at %r' % (
404 patch_repo, patch_ref, base_rev, self.checkout_path))
405 self.Print('git returned non-zero exit status %s:\n%s' % (
406 e.returncode, e.stderr))
407 # Print the current status so that developers know what changes caused the
408 # patch failure, since git cherry-pick doesn't show that information.
409 self.Print(self._Capture(['status']))
410 self._Capture(['cherry-pick', '--abort'])
411 raise
412
Edward Lesmesc621b212018-03-21 20:26:56 -0400413 if options.reset_patch_ref:
414 self._Capture(['reset', '--soft', base_rev])
415
msb@chromium.orge28e4982009-09-25 20:51:45 +0000416 def update(self, options, args, file_list):
417 """Runs git to update or transparently checkout the working copy.
418
419 All updated files will be appended to file_list.
420
421 Raises:
422 Error: if can't get URL for relative path.
423 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000424 if args:
425 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
426
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000427 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000428
John Budorick882c91e2018-07-12 22:11:41 +0000429 # If a dependency is not pinned, track the default remote branch.
430 default_rev = 'refs/remotes/%s/master' % self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000431 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000432 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000433 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000434 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000435 # Override the revision number.
436 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000437 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000438 # Check again for a revision in case an initial ref was specified
439 # in the url, for example bla.git@refs/heads/custombranch
440 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000441 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000442 if not revision:
443 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000444
szager@chromium.org8a139702014-06-20 15:55:01 +0000445 if managed:
446 self._DisableHooks()
447
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000448 printed_path = False
449 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000450 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700451 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000452 verbose = ['--verbose']
453 printed_path = True
454
John Budorick882c91e2018-07-12 22:11:41 +0000455 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
456 if remote_ref:
457 # Rewrite remote refs to their local equivalents.
458 revision = ''.join(remote_ref)
459 rev_type = "branch"
460 elif revision.startswith('refs/'):
461 # Local branch? We probably don't want to support, since DEPS should
462 # always specify branches as they are in the upstream repo.
463 rev_type = "branch"
464 else:
465 # hash is also a tag, only make a distinction at checkout
466 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000467
John Budorick882c91e2018-07-12 22:11:41 +0000468 mirror = self._GetMirror(url, options)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000469 if mirror:
470 url = mirror.mirror_path
471
primiano@chromium.org1c127382015-02-17 11:15:40 +0000472 # If we are going to introduce a new project, there is a possibility that
473 # we are syncing back to a state where the project was originally a
474 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
475 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
476 # In such case, we might have a backup of the former .git folder, which can
477 # be used to avoid re-fetching the entire repo again (useful for bisects).
478 backup_dir = self.GetGitBackupDirPath()
479 target_dir = os.path.join(self.checkout_path, '.git')
480 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
481 gclient_utils.safe_makedirs(self.checkout_path)
482 os.rename(backup_dir, target_dir)
483 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800484 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000485
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000486 if (not os.path.exists(self.checkout_path) or
487 (os.path.isdir(self.checkout_path) and
488 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000489 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000490 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000491 try:
John Budorick882c91e2018-07-12 22:11:41 +0000492 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000493 except subprocess2.CalledProcessError:
494 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000495 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000496 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800497 files = self._Capture(
498 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000499 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000500 if not verbose:
501 # Make the output a little prettier. It's nice to have some whitespace
502 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000503 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000504 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000505
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000506 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000507 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000508 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
509 return self._Capture(['rev-parse', '--verify', 'HEAD'])
510
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000511 self._maybe_break_locks(options)
512
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000513 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000514 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000515
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000516 # See if the url has changed (the unittests use git://foo for the url, let
517 # that through).
518 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
519 return_early = False
520 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
521 # unit test pass. (and update the comment above)
522 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
523 # This allows devs to use experimental repos which have a different url
524 # but whose branch(s) are the same as official repos.
borenet@google.comb09097a2014-04-09 19:09:08 +0000525 if (current_url.rstrip('/') != url.rstrip('/') and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000526 url != 'git://foo' and
527 subprocess2.capture(
528 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
529 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000530 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000531 if not (options.force or options.reset):
532 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700533 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000534 # Switch over to the new upstream
535 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000536 if mirror:
537 with open(os.path.join(
538 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
539 'w') as fh:
540 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000541 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
542 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000543
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000544 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000545 else:
John Budorick882c91e2018-07-12 22:11:41 +0000546 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000547
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000548 if return_early:
549 return self._Capture(['rev-parse', '--verify', 'HEAD'])
550
msb@chromium.org5bde4852009-12-14 16:47:12 +0000551 cur_branch = self._GetCurrentBranch()
552
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000553 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000554 # 0) HEAD is detached. Probably from our initial clone.
555 # - make sure HEAD is contained by a named ref, then update.
556 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700557 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000558 # - try to rebase onto the new hash or branch
559 # 2) current branch is tracking a remote branch with local committed
560 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000561 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000562 # 3) current branch is tracking a remote branch w/or w/out changes, and
563 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000564 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000565 # 4) current branch is tracking a remote branch, but DEPS switches to a
566 # different remote branch, and
567 # a) current branch has no local changes, and --force:
568 # - checkout new branch
569 # b) current branch has local changes, and --force and --reset:
570 # - checkout new branch
571 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000572
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000573 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
574 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000575 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
576 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000577 if cur_branch is None:
578 upstream_branch = None
579 current_type = "detached"
580 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000581 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000582 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
583 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
584 current_type = "hash"
585 logging.debug("Current branch is not tracking an upstream (remote)"
586 " branch.")
587 elif upstream_branch.startswith('refs/remotes'):
588 current_type = "branch"
589 else:
590 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000591
Edward Lemur579c9862018-07-13 23:17:51 +0000592 self._SetFetchConfig(options)
593 self._Fetch(options, prune=options.force)
594
John Budorick882c91e2018-07-12 22:11:41 +0000595 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000596 # Update the remotes first so we have all the refs.
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000597 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000598 cwd=self.checkout_path)
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000599 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000600 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000601
John Budorick882c91e2018-07-12 22:11:41 +0000602 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200603
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000604 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000605 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000606 target = 'HEAD'
607 if options.upstream and upstream_branch:
608 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800609 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000610
msb@chromium.org786fb682010-06-02 15:16:23 +0000611 if current_type == 'detached':
612 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800613 # We just did a Scrub, this is as clean as it's going to get. In
614 # particular if HEAD is a commit that contains two versions of the same
615 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
616 # to actually "Clean" the checkout; that commit is uncheckoutable on this
617 # system. The best we can do is carry forward to the checkout step.
618 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000619 self._CheckClean(revision)
620 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000621 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000622 self.Print('Up-to-date; skipping checkout.')
623 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000624 # 'git checkout' may need to overwrite existing untracked files. Allow
625 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000626 self._Checkout(
627 options,
John Budorick882c91e2018-07-12 22:11:41 +0000628 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000629 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000630 quiet=True,
631 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000632 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000633 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000634 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000635 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700636 # Can't find a merge-base since we don't know our upstream. That makes
637 # this command VERY likely to produce a rebase failure. For now we
638 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000639 upstream_branch = self.remote
640 if options.revision or deps_revision:
641 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700642 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700643 printed_path=printed_path, merge=options.merge)
644 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000645 elif rev_type == 'hash':
646 # case 2
647 self._AttemptRebase(upstream_branch, file_list, options,
648 newbase=revision, printed_path=printed_path,
649 merge=options.merge)
650 printed_path = True
651 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000652 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000653 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000654 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000655 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000656 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000657 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000658 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000659 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
660 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000661 force_switch = False
662 if options.force:
663 try:
John Budorick882c91e2018-07-12 22:11:41 +0000664 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000665 # case 4a
666 force_switch = True
667 except gclient_utils.Error as e:
668 if options.reset:
669 # case 4b
670 force_switch = True
671 else:
672 switch_error = '%s\n%s' % (e.message, switch_error)
673 if force_switch:
674 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000675 (upstream_branch, new_base))
676 switch_branch = 'gclient_' + remote_ref[1]
677 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000678 self._Checkout(options, switch_branch, force=True, quiet=True)
679 else:
680 # case 4c
681 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000682 else:
683 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800684 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000685 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000686 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000687 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000688 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000689 if options.merge:
690 merge_args.append('--ff')
691 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000692 merge_args.append('--ff-only')
693 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000694 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000695 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700696 rebase_files = []
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000697 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
698 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000699 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700700 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000701 printed_path = True
702 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000703 if not options.auto_rebase:
704 try:
705 action = self._AskForData(
706 'Cannot %s, attempt to rebase? '
707 '(y)es / (q)uit / (s)kip : ' %
708 ('merge' if options.merge else 'fast-forward merge'),
709 options)
710 except ValueError:
711 raise gclient_utils.Error('Invalid Character')
712 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700713 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000714 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000715 printed_path = True
716 break
717 elif re.match(r'quit|q', action, re.I):
718 raise gclient_utils.Error("Can't fast-forward, please merge or "
719 "rebase manually.\n"
720 "cd %s && git " % self.checkout_path
721 + "rebase %s" % upstream_branch)
722 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000723 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000724 return
725 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000726 self.Print('Input not recognized')
smut@google.com27c9c8a2014-09-11 19:57:55 +0000727 elif re.match("error: Your local changes to '.*' would be "
728 "overwritten by merge. Aborting.\nPlease, commit your "
729 "changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000730 e.stderr):
731 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000732 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700733 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000734 printed_path = True
735 raise gclient_utils.Error(e.stderr)
736 else:
737 # Some other problem happened with the merge
738 logging.error("Error during fast-forward merge in %s!" % self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000739 self.Print(e.stderr)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000740 raise
741 else:
742 # Fast-forward merge was successful
743 if not re.match('Already up-to-date.', merge_output) or verbose:
744 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700745 self.Print('_____ %s at %s' % (self.relpath, revision),
746 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000747 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000748 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000749 if not verbose:
750 # Make the output a little prettier. It's nice to have some
751 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000752 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000753
agablec3937b92016-10-25 10:13:03 -0700754 if file_list is not None:
755 file_list.extend(
756 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000757
758 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000759 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700760 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000761 '\nConflict while rebasing this branch.\n'
762 'Fix the conflict and run gclient again.\n'
763 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700764 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000765
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000766 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000767 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
768 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000769
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000770 # If --reset and --delete_unversioned_trees are specified, remove any
771 # untracked directories.
772 if options.reset and options.delete_unversioned_trees:
773 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
774 # merge-base by default), so doesn't include untracked files. So we use
775 # 'git ls-files --directory --others --exclude-standard' here directly.
776 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800777 ['-c', 'core.quotePath=false', 'ls-files',
778 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000779 self.checkout_path)
780 for path in (p for p in paths.splitlines() if p.endswith('/')):
781 full_path = os.path.join(self.checkout_path, path)
782 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000783 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000784 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000785
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000786 return self._Capture(['rev-parse', '--verify', 'HEAD'])
787
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000788 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000789 """Reverts local modifications.
790
791 All reverted files will be appended to file_list.
792 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000793 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000794 # revert won't work if the directory doesn't exist. It needs to
795 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000796 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000797 # Don't reuse the args.
798 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000799
800 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000801 if options.upstream:
802 if self._GetCurrentBranch():
803 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
804 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000805 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000806 if not deps_revision:
807 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000808 if deps_revision.startswith('refs/heads/'):
809 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700810 try:
811 deps_revision = self.GetUsableRev(deps_revision, options)
812 except NoUsableRevError as e:
813 # If the DEPS entry's url and hash changed, try to update the origin.
814 # See also http://crbug.com/520067.
815 logging.warn(
816 'Couldn\'t find usable revision, will retrying to update instead: %s',
817 e.message)
818 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000819
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000820 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800821 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000822
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800823 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000824 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000825
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000826 if file_list is not None:
827 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
828
829 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000830 """Returns revision"""
831 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000832
msb@chromium.orge28e4982009-09-25 20:51:45 +0000833 def runhooks(self, options, args, file_list):
834 self.status(options, args, file_list)
835
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000836 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000837 """Display status information."""
838 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000839 self.Print('________ couldn\'t run status in %s:\n'
840 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000841 else:
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700842 try:
843 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
844 except subprocess2.CalledProcessError:
845 merge_base = []
Aaron Gablef4068aa2017-12-12 15:14:09 -0800846 self._Run(
847 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
848 options, stdout=self.out_fh, always=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000849 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800850 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000851 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000852
smutae7ea312016-07-18 11:59:41 -0700853 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700854 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700855 sha1 = None
856 if not os.path.isdir(self.checkout_path):
857 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800858 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700859
860 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
861 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700862 else:
agable41e3a6c2016-10-20 11:36:56 -0700863 # May exist in origin, but we don't have it yet, so fetch and look
864 # again.
865 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700866 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
867 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700868
869 if not sha1:
870 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800871 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700872
873 return sha1
874
primiano@chromium.org1c127382015-02-17 11:15:40 +0000875 def GetGitBackupDirPath(self):
876 """Returns the path where the .git folder for the current project can be
877 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
878 return os.path.join(self._root_dir,
879 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
880
John Budorick882c91e2018-07-12 22:11:41 +0000881 def _GetMirror(self, url, options):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000882 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000883 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000884 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000885 mirror_kwargs = {
886 'print_func': self.filter,
John Budorick882c91e2018-07-12 22:11:41 +0000887 'refs': []
hinoka@google.comb1b54572014-04-16 22:29:23 +0000888 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000889 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
890 mirror_kwargs['refs'].append('refs/branch-heads/*')
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000891 if hasattr(options, 'with_tags') and options.with_tags:
892 mirror_kwargs['refs'].append('refs/tags/*')
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000893 return git_cache.Mirror(url, **mirror_kwargs)
894
John Budorick882c91e2018-07-12 22:11:41 +0000895 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800896 """Update a git mirror by fetching the latest commits from the remote,
897 unless mirror already contains revision whose type is sha1 hash.
898 """
John Budorick882c91e2018-07-12 22:11:41 +0000899 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800900 if options.verbose:
901 self.Print('skipping mirror update, it has rev=%s already' % revision,
902 timestamp=False)
903 return
904
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000905 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000906 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000907 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000908 depth = 10
909 else:
910 depth = 10000
911 else:
912 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000913 mirror.populate(verbose=options.verbose,
914 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000915 depth=depth,
916 ignore_lock=getattr(options, 'ignore_locks', False),
917 lock_timeout=getattr(options, 'lock_timeout', 0))
918 mirror.unlock()
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000919
John Budorick882c91e2018-07-12 22:11:41 +0000920 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000921 """Clone a git repository from the given URL.
922
msb@chromium.org786fb682010-06-02 15:16:23 +0000923 Once we've cloned the repo, we checkout a working branch if the specified
924 revision is a branch head. If it is a tag or a specific commit, then we
925 leave HEAD detached as it makes future updates simpler -- in this case the
926 user should first create a new branch or switch to an existing branch before
927 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000928 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000929 # git clone doesn't seem to insert a newline properly before printing
930 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000931 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +0000932 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +0000933 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000934 if self.cache_dir:
935 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000936 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000937 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000938 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +0000939 # If the parent directory does not exist, Git clone on Windows will not
940 # create it, so we need to do it manually.
941 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000942 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000943
944 template_dir = None
945 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +0000946 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000947 # In the case of a subproject, the pinned sha is not necessarily the
948 # head of the remote branch (so we can't just use --depth=N). Instead,
949 # we tell git to fetch all the remote objects from SHA..HEAD by means of
950 # a template git dir which has a 'shallow' file pointing to the sha.
951 template_dir = tempfile.mkdtemp(
952 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
953 dir=parent_dir)
954 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
955 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
956 template_file.write(revision)
957 clone_cmd.append('--template=' + template_dir)
958 else:
959 # Otherwise, we're just interested in the HEAD. Just use --depth.
960 clone_cmd.append('--depth=1')
961
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000962 tmp_dir = tempfile.mkdtemp(
963 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
964 dir=parent_dir)
965 try:
966 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +0100967 if self.print_outbuf:
968 print_stdout = True
969 stdout = gclient_utils.WriteToStdout(self.out_fh)
970 else:
971 print_stdout = False
972 stdout = self.out_fh
973 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
974 print_stdout=print_stdout, stdout=stdout)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000975 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000976 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
977 os.path.join(self.checkout_path, '.git'))
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000978 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000979 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000980 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000981 finally:
982 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000983 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000984 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000985 if template_dir:
986 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +0000987 self._SetFetchConfig(options)
988 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000989 revision = self._AutoFetchRef(options, revision)
990 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
991 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000992 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +0000993 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000994 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +0000995 ('Checked out %s to a detached HEAD. Before making any commits\n'
996 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
997 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +0000998 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000999
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001000 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001001 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001002 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001003 raise gclient_utils.Error("Background task requires input. Rerun "
1004 "gclient with --jobs=1 so that\n"
1005 "interaction is possible.")
1006 try:
1007 return raw_input(prompt)
1008 except KeyboardInterrupt:
1009 # Hide the exception.
1010 sys.exit(1)
1011
1012
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001013 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001014 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001015 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001016 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001017 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001018 revision = upstream
1019 if newbase:
1020 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001021 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001022 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001023 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001024 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001025 printed_path = True
1026 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001027 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001028
1029 if merge:
1030 merge_output = self._Capture(['merge', revision])
1031 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001032 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001033 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001034
1035 # Build the rebase command here using the args
1036 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1037 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001038 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001039 rebase_cmd.append('--verbose')
1040 if newbase:
1041 rebase_cmd.extend(['--onto', newbase])
1042 rebase_cmd.append(upstream)
1043 if branch:
1044 rebase_cmd.append(branch)
1045
1046 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001047 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001048 except subprocess2.CalledProcessError, e:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001049 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
1050 re.match(r'cannot rebase: your index contains uncommitted changes',
1051 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001052 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001053 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001054 'Cannot rebase because of unstaged changes.\n'
1055 '\'git reset --hard HEAD\' ?\n'
1056 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001057 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001058 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001059 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001060 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001061 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001062 break
1063 elif re.match(r'quit|q', rebase_action, re.I):
1064 raise gclient_utils.Error("Please merge or rebase manually\n"
1065 "cd %s && git " % self.checkout_path
1066 + "%s" % ' '.join(rebase_cmd))
1067 elif re.match(r'show|s', rebase_action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001068 self.Print('%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001069 continue
1070 else:
1071 gclient_utils.Error("Input not recognized")
1072 continue
1073 elif re.search(r'^CONFLICT', e.stdout, re.M):
1074 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1075 "Fix the conflict and run gclient again.\n"
1076 "See 'man git-rebase' for details.\n")
1077 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001078 self.Print(e.stdout.strip())
1079 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001080 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1081 "manually.\ncd %s && git " %
1082 self.checkout_path
1083 + "%s" % ' '.join(rebase_cmd))
1084
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001085 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001086 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001087 # Make the output a little prettier. It's nice to have some
1088 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001089 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001090
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001091 @staticmethod
1092 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001093 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1094 if not ok:
1095 raise gclient_utils.Error('git version %s < minimum required %s' %
1096 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001097
John Budorick882c91e2018-07-12 22:11:41 +00001098 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001099 # Special case handling if all 3 conditions are met:
1100 # * the mirros have recently changed, but deps destination remains same,
1101 # * the git histories of mirrors are conflicting.
1102 # * git cache is used
1103 # This manifests itself in current checkout having invalid HEAD commit on
1104 # most git operations. Since git cache is used, just deleted the .git
1105 # folder, and re-create it by cloning.
1106 try:
1107 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1108 except subprocess2.CalledProcessError as e:
1109 if ('fatal: bad object HEAD' in e.stderr
1110 and self.cache_dir and self.cache_dir in url):
1111 self.Print((
1112 'Likely due to DEPS change with git cache_dir, '
1113 'the current commit points to no longer existing object.\n'
1114 '%s' % e)
1115 )
1116 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001117 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001118 else:
1119 raise
1120
msb@chromium.org786fb682010-06-02 15:16:23 +00001121 def _IsRebasing(self):
1122 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1123 # have a plumbing command to determine whether a rebase is in progress, so
1124 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1125 g = os.path.join(self.checkout_path, '.git')
1126 return (
1127 os.path.isdir(os.path.join(g, "rebase-merge")) or
1128 os.path.isdir(os.path.join(g, "rebase-apply")))
1129
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001130 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001131 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1132 if os.path.exists(lockfile):
1133 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001134 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001135 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1136 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001137 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001138
msb@chromium.org786fb682010-06-02 15:16:23 +00001139 # Make sure the tree is clean; see git-rebase.sh for reference
1140 try:
1141 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001142 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001143 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001144 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001145 '\tYou have unstaged changes.\n'
1146 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001147 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001148 try:
1149 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001150 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001151 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001152 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001153 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001154 '\tYour index contains uncommitted changes\n'
1155 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001156 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001157
agable83faed02016-10-24 14:37:10 -07001158 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001159 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1160 # reference by a commit). If not, error out -- most likely a rebase is
1161 # in progress, try to detect so we can give a better error.
1162 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001163 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1164 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001165 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001166 # Commit is not contained by any rev. See if the user is rebasing:
1167 if self._IsRebasing():
1168 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001169 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001170 '\tAlready in a conflict, i.e. (no branch).\n'
1171 '\tFix the conflict and run gclient again.\n'
1172 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1173 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001174 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001175 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001176 name = ('saved-by-gclient-' +
1177 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001178 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001179 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001180 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001181
msb@chromium.org5bde4852009-12-14 16:47:12 +00001182 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001183 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001184 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001185 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001186 return None
1187 return branch
1188
borenet@google.comc3e09d22014-04-10 13:58:18 +00001189 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001190 kwargs.setdefault('cwd', self.checkout_path)
1191 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001192 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001193 env = scm.GIT.ApplyEnvVars(kwargs)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001194 ret = subprocess2.check_output(['git'] + args, env=env, **kwargs)
1195 if strip:
1196 ret = ret.strip()
1197 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001198
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001199 def _Checkout(self, options, ref, force=False, quiet=None):
1200 """Performs a 'git-checkout' operation.
1201
1202 Args:
1203 options: The configured option set
1204 ref: (str) The branch/commit to checkout
1205 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1206 'None', the behavior is inferred from 'options.verbose'.
1207 Returns: (str) The output of the checkout operation
1208 """
1209 if quiet is None:
1210 quiet = (not options.verbose)
1211 checkout_args = ['checkout']
1212 if force:
1213 checkout_args.append('--force')
1214 if quiet:
1215 checkout_args.append('--quiet')
1216 checkout_args.append(ref)
1217 return self._Capture(checkout_args)
1218
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001219 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1220 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001221 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemurd64781e2018-07-11 23:09:55 +00001222 # When a mirror is configured, it fetches only the refs/heads, and possibly
1223 # the refs/branch-heads and refs/tags, but not the refs/changes. So, if
1224 # we're asked to fetch a refs/changes ref from the mirror, it won't have it.
1225 # This makes sure that we always fetch refs/changes directly from the
1226 # repository and not from the mirror.
1227 if refspec and refspec.startswith('refs/changes'):
1228 remote, _ = gclient_utils.SplitUrlRevision(self.url)
1229 # Make sure that we fetch the (remote) refs/changes/xx ref to the (local)
1230 # refs/changes/xx ref.
1231 if ':' not in refspec:
1232 refspec += ':' + refspec
dnj@chromium.org680f2172014-06-25 00:39:32 +00001233 fetch_cmd = cfg + [
1234 'fetch',
1235 remote or self.remote,
1236 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001237 if refspec:
1238 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001239
1240 if prune:
1241 fetch_cmd.append('--prune')
1242 if options.verbose:
1243 fetch_cmd.append('--verbose')
1244 elif quiet:
1245 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001246 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001247
1248 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1249 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1250
Edward Lemur579c9862018-07-13 23:17:51 +00001251 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001252 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1253 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001254 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001255 try:
1256 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1257 options)
1258 self._Run(['config', 'remote.%s.fetch' % self.remote,
1259 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1260 except subprocess2.CalledProcessError as e:
1261 # If exit code was 5, it means we attempted to unset a config that
1262 # didn't exist. Ignore it.
1263 if e.returncode != 5:
1264 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001265 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001266 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001267 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1268 '^\\+refs/branch-heads/\\*:.*$']
1269 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001270 if hasattr(options, 'with_tags') and options.with_tags:
1271 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1272 '+refs/tags/*:refs/tags/*',
1273 '^\\+refs/tags/\\*:.*$']
1274 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001275
John Budorick882c91e2018-07-12 22:11:41 +00001276 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001277 """Attempts to fetch |revision| if not available in local repo.
1278
1279 Returns possibly updated revision."""
John Budorick882c91e2018-07-12 22:11:41 +00001280 try:
1281 self._Capture(['rev-parse', revision])
1282 except subprocess2.CalledProcessError:
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001283 self._Fetch(options, refspec=revision)
1284 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1285 return revision
1286
dnj@chromium.org680f2172014-06-25 00:39:32 +00001287 def _Run(self, args, options, show_header=True, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001288 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001289 kwargs.setdefault('cwd', self.checkout_path)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001290 kwargs.setdefault('stdout', self.out_fh)
szager@chromium.org848fd492014-04-09 19:06:44 +00001291 kwargs['filter_fn'] = self.filter
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001292 kwargs.setdefault('print_stdout', False)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001293 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001294 cmd = ['git'] + args
dnj@chromium.org680f2172014-06-25 00:39:32 +00001295 if show_header:
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001296 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1297 else:
1298 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001299
1300
1301class CipdPackage(object):
1302 """A representation of a single CIPD package."""
1303
John Budorickd3ba72b2018-03-20 12:27:42 -07001304 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001305 self._authority_for_subdir = authority_for_subdir
1306 self._name = name
1307 self._version = version
1308
1309 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001310 def authority_for_subdir(self):
1311 """Whether this package has authority to act on behalf of its subdir.
1312
1313 Some operations should only be performed once per subdirectory. A package
1314 that has authority for its subdirectory is the only package that should
1315 perform such operations.
1316
1317 Returns:
1318 bool; whether this package has subdir authority.
1319 """
1320 return self._authority_for_subdir
1321
1322 @property
1323 def name(self):
1324 return self._name
1325
1326 @property
1327 def version(self):
1328 return self._version
1329
1330
1331class CipdRoot(object):
1332 """A representation of a single CIPD root."""
1333 def __init__(self, root_dir, service_url):
1334 self._all_packages = set()
1335 self._mutator_lock = threading.Lock()
1336 self._packages_by_subdir = collections.defaultdict(list)
1337 self._root_dir = root_dir
1338 self._service_url = service_url
1339
1340 def add_package(self, subdir, package, version):
1341 """Adds a package to this CIPD root.
1342
1343 As far as clients are concerned, this grants both root and subdir authority
1344 to packages arbitrarily. (The implementation grants root authority to the
1345 first package added and subdir authority to the first package added for that
1346 subdir, but clients should not depend on or expect that behavior.)
1347
1348 Args:
1349 subdir: str; relative path to where the package should be installed from
1350 the cipd root directory.
1351 package: str; the cipd package name.
1352 version: str; the cipd package version.
1353 Returns:
1354 CipdPackage; the package that was created and added to this root.
1355 """
1356 with self._mutator_lock:
1357 cipd_package = CipdPackage(
1358 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001359 not self._packages_by_subdir[subdir])
1360 self._all_packages.add(cipd_package)
1361 self._packages_by_subdir[subdir].append(cipd_package)
1362 return cipd_package
1363
1364 def packages(self, subdir):
1365 """Get the list of configured packages for the given subdir."""
1366 return list(self._packages_by_subdir[subdir])
1367
1368 def clobber(self):
1369 """Remove the .cipd directory.
1370
1371 This is useful for forcing ensure to redownload and reinitialize all
1372 packages.
1373 """
1374 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001375 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001376 try:
1377 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1378 except OSError:
1379 if os.path.exists(cipd_cache_dir):
1380 raise
1381
1382 @contextlib.contextmanager
1383 def _create_ensure_file(self):
1384 try:
1385 ensure_file = None
1386 with tempfile.NamedTemporaryFile(
1387 suffix='.ensure', delete=False) as ensure_file:
John Budorick302bb842018-07-17 23:49:17 +00001388 ensure_file.write('$ParanoidMode CheckPresence\n\n')
John Budorick0f7b2002018-01-19 15:46:17 -08001389 for subdir, packages in sorted(self._packages_by_subdir.iteritems()):
1390 ensure_file.write('@Subdir %s\n' % subdir)
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001391 for package in sorted(packages, key=lambda p: p.name):
John Budorick0f7b2002018-01-19 15:46:17 -08001392 ensure_file.write('%s %s\n' % (package.name, package.version))
1393 ensure_file.write('\n')
1394 yield ensure_file.name
1395 finally:
1396 if ensure_file is not None and os.path.exists(ensure_file.name):
1397 os.remove(ensure_file.name)
1398
1399 def ensure(self):
1400 """Run `cipd ensure`."""
1401 with self._mutator_lock:
1402 with self._create_ensure_file() as ensure_file:
1403 cmd = [
1404 'cipd', 'ensure',
1405 '-log-level', 'error',
1406 '-root', self.root_dir,
1407 '-ensure-file', ensure_file,
1408 ]
1409 gclient_utils.CheckCallAndFilterAndHeader(cmd)
1410
John Budorickd3ba72b2018-03-20 12:27:42 -07001411 def run(self, command):
1412 if command == 'update':
1413 self.ensure()
1414 elif command == 'revert':
1415 self.clobber()
1416 self.ensure()
1417
John Budorick0f7b2002018-01-19 15:46:17 -08001418 def created_package(self, package):
1419 """Checks whether this root created the given package.
1420
1421 Args:
1422 package: CipdPackage; the package to check.
1423 Returns:
1424 bool; whether this root created the given package.
1425 """
1426 return package in self._all_packages
1427
1428 @property
1429 def root_dir(self):
1430 return self._root_dir
1431
1432 @property
1433 def service_url(self):
1434 return self._service_url
1435
1436
1437class CipdWrapper(SCMWrapper):
1438 """Wrapper for CIPD.
1439
1440 Currently only supports chrome-infra-packages.appspot.com.
1441 """
John Budorick3929e9e2018-02-04 18:18:07 -08001442 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001443
1444 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1445 out_cb=None, root=None, package=None):
1446 super(CipdWrapper, self).__init__(
1447 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1448 out_cb=out_cb)
1449 assert root.created_package(package)
1450 self._package = package
1451 self._root = root
1452
1453 #override
1454 def GetCacheMirror(self):
1455 return None
1456
1457 #override
1458 def GetActualRemoteURL(self, options):
1459 return self._root.service_url
1460
1461 #override
1462 def DoesRemoteURLMatch(self, options):
1463 del options
1464 return True
1465
1466 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001467 """Does nothing.
1468
1469 CIPD packages should be reverted at the root by running
1470 `CipdRoot.run('revert')`.
1471 """
1472 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001473
1474 def diff(self, options, args, file_list):
1475 """CIPD has no notion of diffing."""
1476 pass
1477
1478 def pack(self, options, args, file_list):
1479 """CIPD has no notion of diffing."""
1480 pass
1481
1482 def revinfo(self, options, args, file_list):
1483 """Grab the instance ID."""
1484 try:
1485 tmpdir = tempfile.mkdtemp()
1486 describe_json_path = os.path.join(tmpdir, 'describe.json')
1487 cmd = [
1488 'cipd', 'describe',
1489 self._package.name,
1490 '-log-level', 'error',
1491 '-version', self._package.version,
1492 '-json-output', describe_json_path
1493 ]
1494 gclient_utils.CheckCallAndFilter(
1495 cmd, filter_fn=lambda _line: None, print_stdout=False)
1496 with open(describe_json_path) as f:
1497 describe_json = json.load(f)
1498 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1499 finally:
1500 gclient_utils.rmtree(tmpdir)
1501
1502 def status(self, options, args, file_list):
1503 pass
1504
1505 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001506 """Does nothing.
1507
1508 CIPD packages should be updated at the root by running
1509 `CipdRoot.run('update')`.
1510 """
1511 pass