blob: 3ca455778a2af74ee80e8604840466907f86139c [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 Lemurca7d8812018-07-24 17:42:45 +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):
Edward Lemurca7d8812018-07-24 17:42:45 +0000368 # Abort any cherry-picks in progress.
369 try:
370 self._Capture(['cherry-pick', '--abort'])
371 except subprocess2.CalledProcessError:
372 pass
373
Edward Lesmesc621b212018-03-21 20:26:56 -0400374 base_rev = self._Capture(['rev-parse', 'HEAD'])
Edward Lemurca7d8812018-07-24 17:42:45 +0000375 branch_rev = self._GetBranchForCommit(base_rev)
Edward Lesmesc621b212018-03-21 20:26:56 -0400376 self.Print('===Applying patch ref===')
Edward Lemurca7d8812018-07-24 17:42:45 +0000377 self.Print('Repo is %r @ %r, ref is %r (%r), root is %r' % (
378 patch_repo, patch_ref, base_rev, branch_rev, self.checkout_path))
Edward Lesmesc621b212018-03-21 20:26:56 -0400379 self._Capture(['reset', '--hard'])
380 self._Capture(['fetch', patch_repo, patch_ref])
Edward Lemurca7d8812018-07-24 17:42:45 +0000381 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400382
Edward Lemurca7d8812018-07-24 17:42:45 +0000383 try:
384 if not options.rebase_patch_ref:
385 self._Capture(['checkout', patch_rev])
386 else:
387 # Find the merge-base between the branch_rev and patch_rev to find out
388 # the changes we need to cherry-pick on top of base_rev.
389 merge_base = self._Capture(['merge-base', branch_rev, patch_rev])
390 self.Print('Merge base of %s and %s is %s' % (
391 branch_rev, patch_rev, merge_base))
392 if merge_base == patch_rev:
393 # If the merge-base is patch_rev, it means patch_rev is already part
394 # of the history, so just check it out.
395 self._Capture(['checkout', patch_rev])
396 else:
397 # If a change was uploaded on top of another change, which has already
398 # landed, one of the commits in the cherry-pick range will be
399 # redundant, since it has already landed and its changes incorporated
400 # in the tree.
401 # We pass '--keep-redundant-commits' to ignore those changes.
402 self._Capture(['cherry-pick', merge_base + '..' + patch_rev,
403 '--keep-redundant-commits'])
404
405 if file_list is not None:
406 file_list.extend(self._GetDiffFilenames(base_rev))
407
408 except subprocess2.CalledProcessError as e:
409 self.Print('Failed to apply %r @ %r to %r at %r' % (
410 patch_repo, patch_ref, base_rev, self.checkout_path))
411 self.Print('git returned non-zero exit status %s:\n%s' % (
412 e.returncode, e.stderr))
413 # Print the current status so that developers know what changes caused the
414 # patch failure, since git cherry-pick doesn't show that information.
415 self.Print(self._Capture(['status']))
John Budorick2c984a02018-07-18 23:24:13 +0000416 try:
Edward Lemurca7d8812018-07-24 17:42:45 +0000417 self._Capture(['cherry-pick', '--abort'])
418 except subprocess2.CalledProcessError:
419 pass
420 raise
421
Edward Lesmesc621b212018-03-21 20:26:56 -0400422 if options.reset_patch_ref:
423 self._Capture(['reset', '--soft', base_rev])
424
msb@chromium.orge28e4982009-09-25 20:51:45 +0000425 def update(self, options, args, file_list):
426 """Runs git to update or transparently checkout the working copy.
427
428 All updated files will be appended to file_list.
429
430 Raises:
431 Error: if can't get URL for relative path.
432 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000433 if args:
434 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
435
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000436 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000437
John Budorick882c91e2018-07-12 22:11:41 +0000438 # If a dependency is not pinned, track the default remote branch.
439 default_rev = 'refs/remotes/%s/master' % self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000440 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000441 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000442 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000443 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000444 # Override the revision number.
445 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000446 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000447 # Check again for a revision in case an initial ref was specified
448 # in the url, for example bla.git@refs/heads/custombranch
449 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000450 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000451 if not revision:
452 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000453
szager@chromium.org8a139702014-06-20 15:55:01 +0000454 if managed:
455 self._DisableHooks()
456
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000457 printed_path = False
458 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000459 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700460 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000461 verbose = ['--verbose']
462 printed_path = True
463
John Budorick882c91e2018-07-12 22:11:41 +0000464 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
465 if remote_ref:
466 # Rewrite remote refs to their local equivalents.
467 revision = ''.join(remote_ref)
468 rev_type = "branch"
469 elif revision.startswith('refs/'):
470 # Local branch? We probably don't want to support, since DEPS should
471 # always specify branches as they are in the upstream repo.
472 rev_type = "branch"
473 else:
474 # hash is also a tag, only make a distinction at checkout
475 rev_type = "hash"
smut@google.comd33eab32014-07-07 19:35:18 +0000476
John Budorick882c91e2018-07-12 22:11:41 +0000477 mirror = self._GetMirror(url, options)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000478 if mirror:
479 url = mirror.mirror_path
480
primiano@chromium.org1c127382015-02-17 11:15:40 +0000481 # If we are going to introduce a new project, there is a possibility that
482 # we are syncing back to a state where the project was originally a
483 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
484 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
485 # In such case, we might have a backup of the former .git folder, which can
486 # be used to avoid re-fetching the entire repo again (useful for bisects).
487 backup_dir = self.GetGitBackupDirPath()
488 target_dir = os.path.join(self.checkout_path, '.git')
489 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
490 gclient_utils.safe_makedirs(self.checkout_path)
491 os.rename(backup_dir, target_dir)
492 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800493 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000494
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000495 if (not os.path.exists(self.checkout_path) or
496 (os.path.isdir(self.checkout_path) and
497 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000498 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000499 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000500 try:
John Budorick882c91e2018-07-12 22:11:41 +0000501 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000502 except subprocess2.CalledProcessError:
503 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000504 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000505 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800506 files = self._Capture(
507 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000508 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000509 if not verbose:
510 # Make the output a little prettier. It's nice to have some whitespace
511 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000512 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000513 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000514
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000515 if not managed:
Edward Lemur579c9862018-07-13 23:17:51 +0000516 self._SetFetchConfig(options)
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000517 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
518 return self._Capture(['rev-parse', '--verify', 'HEAD'])
519
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000520 self._maybe_break_locks(options)
521
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000522 if mirror:
John Budorick882c91e2018-07-12 22:11:41 +0000523 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000524
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000525 # See if the url has changed (the unittests use git://foo for the url, let
526 # that through).
527 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
528 return_early = False
529 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
530 # unit test pass. (and update the comment above)
531 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
532 # This allows devs to use experimental repos which have a different url
533 # but whose branch(s) are the same as official repos.
borenet@google.comb09097a2014-04-09 19:09:08 +0000534 if (current_url.rstrip('/') != url.rstrip('/') and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000535 url != 'git://foo' and
536 subprocess2.capture(
537 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
538 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000539 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000540 if not (options.force or options.reset):
541 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700542 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000543 # Switch over to the new upstream
544 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000545 if mirror:
546 with open(os.path.join(
547 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
548 'w') as fh:
549 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41 +0000550 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
551 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000552
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000553 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000554 else:
John Budorick882c91e2018-07-12 22:11:41 +0000555 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000556
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000557 if return_early:
558 return self._Capture(['rev-parse', '--verify', 'HEAD'])
559
msb@chromium.org5bde4852009-12-14 16:47:12 +0000560 cur_branch = self._GetCurrentBranch()
561
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000562 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000563 # 0) HEAD is detached. Probably from our initial clone.
564 # - make sure HEAD is contained by a named ref, then update.
565 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700566 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000567 # - try to rebase onto the new hash or branch
568 # 2) current branch is tracking a remote branch with local committed
569 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000570 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000571 # 3) current branch is tracking a remote branch w/or w/out changes, and
572 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000573 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000574 # 4) current branch is tracking a remote branch, but DEPS switches to a
575 # different remote branch, and
576 # a) current branch has no local changes, and --force:
577 # - checkout new branch
578 # b) current branch has local changes, and --force and --reset:
579 # - checkout new branch
580 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000581
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000582 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
583 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000584 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
585 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000586 if cur_branch is None:
587 upstream_branch = None
588 current_type = "detached"
589 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000590 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000591 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
592 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
593 current_type = "hash"
594 logging.debug("Current branch is not tracking an upstream (remote)"
595 " branch.")
596 elif upstream_branch.startswith('refs/remotes'):
597 current_type = "branch"
598 else:
599 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000600
Edward Lemur579c9862018-07-13 23:17:51 +0000601 self._SetFetchConfig(options)
602 self._Fetch(options, prune=options.force)
603
John Budorick882c91e2018-07-12 22:11:41 +0000604 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000605 # Update the remotes first so we have all the refs.
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000606 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000607 cwd=self.checkout_path)
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000608 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000609 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000610
John Budorick882c91e2018-07-12 22:11:41 +0000611 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200612
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000613 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000614 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000615 target = 'HEAD'
616 if options.upstream and upstream_branch:
617 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800618 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000619
msb@chromium.org786fb682010-06-02 15:16:23 +0000620 if current_type == 'detached':
621 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800622 # We just did a Scrub, this is as clean as it's going to get. In
623 # particular if HEAD is a commit that contains two versions of the same
624 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
625 # to actually "Clean" the checkout; that commit is uncheckoutable on this
626 # system. The best we can do is carry forward to the checkout step.
627 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41 +0000628 self._CheckClean(revision)
629 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000630 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000631 self.Print('Up-to-date; skipping checkout.')
632 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000633 # 'git checkout' may need to overwrite existing untracked files. Allow
634 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000635 self._Checkout(
636 options,
John Budorick882c91e2018-07-12 22:11:41 +0000637 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000638 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000639 quiet=True,
640 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000641 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000642 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000643 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000644 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700645 # Can't find a merge-base since we don't know our upstream. That makes
646 # this command VERY likely to produce a rebase failure. For now we
647 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41 +0000648 upstream_branch = self.remote
649 if options.revision or deps_revision:
650 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700651 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700652 printed_path=printed_path, merge=options.merge)
653 printed_path = True
John Budorick882c91e2018-07-12 22:11:41 +0000654 elif rev_type == 'hash':
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
660 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000661 # case 4
John Budorick882c91e2018-07-12 22:11:41 +0000662 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000663 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000664 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000665 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41 +0000666 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000667 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41 +0000668 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
669 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000670 force_switch = False
671 if options.force:
672 try:
John Budorick882c91e2018-07-12 22:11:41 +0000673 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000674 # case 4a
675 force_switch = True
676 except gclient_utils.Error as e:
677 if options.reset:
678 # case 4b
679 force_switch = True
680 else:
681 switch_error = '%s\n%s' % (e.message, switch_error)
682 if force_switch:
683 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41 +0000684 (upstream_branch, new_base))
685 switch_branch = 'gclient_' + remote_ref[1]
686 self._Capture(['branch', '-f', switch_branch, new_base])
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000687 self._Checkout(options, switch_branch, force=True, quiet=True)
688 else:
689 # case 4c
690 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000691 else:
692 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800693 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000694 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000695 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000696 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000697 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000698 if options.merge:
699 merge_args.append('--ff')
700 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000701 merge_args.append('--ff-only')
702 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000703 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000704 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700705 rebase_files = []
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000706 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
707 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000708 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700709 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000710 printed_path = True
711 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000712 if not options.auto_rebase:
713 try:
714 action = self._AskForData(
715 'Cannot %s, attempt to rebase? '
716 '(y)es / (q)uit / (s)kip : ' %
717 ('merge' if options.merge else 'fast-forward merge'),
718 options)
719 except ValueError:
720 raise gclient_utils.Error('Invalid Character')
721 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700722 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000723 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000724 printed_path = True
725 break
726 elif re.match(r'quit|q', action, re.I):
727 raise gclient_utils.Error("Can't fast-forward, please merge or "
728 "rebase manually.\n"
729 "cd %s && git " % self.checkout_path
730 + "rebase %s" % upstream_branch)
731 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000732 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000733 return
734 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000735 self.Print('Input not recognized')
smut@google.com27c9c8a2014-09-11 19:57:55 +0000736 elif re.match("error: Your local changes to '.*' would be "
737 "overwritten by merge. Aborting.\nPlease, commit your "
738 "changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000739 e.stderr):
740 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41 +0000741 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 14:37:10 -0700742 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000743 printed_path = True
744 raise gclient_utils.Error(e.stderr)
745 else:
746 # Some other problem happened with the merge
747 logging.error("Error during fast-forward merge in %s!" % self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000748 self.Print(e.stderr)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000749 raise
750 else:
751 # Fast-forward merge was successful
752 if not re.match('Already up-to-date.', merge_output) or verbose:
753 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700754 self.Print('_____ %s at %s' % (self.relpath, revision),
755 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000756 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000757 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000758 if not verbose:
759 # Make the output a little prettier. It's nice to have some
760 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000761 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000762
agablec3937b92016-10-25 10:13:03 -0700763 if file_list is not None:
764 file_list.extend(
765 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000766
767 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000768 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700769 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000770 '\nConflict while rebasing this branch.\n'
771 'Fix the conflict and run gclient again.\n'
772 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700773 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000774
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000775 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000776 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
777 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000778
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000779 # If --reset and --delete_unversioned_trees are specified, remove any
780 # untracked directories.
781 if options.reset and options.delete_unversioned_trees:
782 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
783 # merge-base by default), so doesn't include untracked files. So we use
784 # 'git ls-files --directory --others --exclude-standard' here directly.
785 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800786 ['-c', 'core.quotePath=false', 'ls-files',
787 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000788 self.checkout_path)
789 for path in (p for p in paths.splitlines() if p.endswith('/')):
790 full_path = os.path.join(self.checkout_path, path)
791 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000792 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000793 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000794
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000795 return self._Capture(['rev-parse', '--verify', 'HEAD'])
796
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000797 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000798 """Reverts local modifications.
799
800 All reverted files will be appended to file_list.
801 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000802 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000803 # revert won't work if the directory doesn't exist. It needs to
804 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000805 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000806 # Don't reuse the args.
807 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000808
809 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000810 if options.upstream:
811 if self._GetCurrentBranch():
812 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
813 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000814 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000815 if not deps_revision:
816 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000817 if deps_revision.startswith('refs/heads/'):
818 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700819 try:
820 deps_revision = self.GetUsableRev(deps_revision, options)
821 except NoUsableRevError as e:
822 # If the DEPS entry's url and hash changed, try to update the origin.
823 # See also http://crbug.com/520067.
824 logging.warn(
825 'Couldn\'t find usable revision, will retrying to update instead: %s',
826 e.message)
827 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000828
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(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000831
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800832 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000833 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000834
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000835 if file_list is not None:
836 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
837
838 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000839 """Returns revision"""
840 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000841
msb@chromium.orge28e4982009-09-25 20:51:45 +0000842 def runhooks(self, options, args, file_list):
843 self.status(options, args, file_list)
844
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000845 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000846 """Display status information."""
847 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000848 self.Print('________ couldn\'t run status in %s:\n'
849 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000850 else:
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700851 try:
852 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
853 except subprocess2.CalledProcessError:
854 merge_base = []
Aaron Gablef4068aa2017-12-12 15:14:09 -0800855 self._Run(
856 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
857 options, stdout=self.out_fh, always=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000858 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800859 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000860 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000861
smutae7ea312016-07-18 11:59:41 -0700862 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700863 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700864 sha1 = None
865 if not os.path.isdir(self.checkout_path):
866 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800867 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700868
869 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
870 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700871 else:
agable41e3a6c2016-10-20 11:36:56 -0700872 # May exist in origin, but we don't have it yet, so fetch and look
873 # again.
874 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700875 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
876 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700877
878 if not sha1:
879 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800880 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700881
882 return sha1
883
primiano@chromium.org1c127382015-02-17 11:15:40 +0000884 def GetGitBackupDirPath(self):
885 """Returns the path where the .git folder for the current project can be
886 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
887 return os.path.join(self._root_dir,
888 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
889
John Budorick882c91e2018-07-12 22:11:41 +0000890 def _GetMirror(self, url, options):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000891 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000892 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000893 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000894 mirror_kwargs = {
895 'print_func': self.filter,
John Budorick882c91e2018-07-12 22:11:41 +0000896 'refs': []
hinoka@google.comb1b54572014-04-16 22:29:23 +0000897 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000898 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
899 mirror_kwargs['refs'].append('refs/branch-heads/*')
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000900 if hasattr(options, 'with_tags') and options.with_tags:
901 mirror_kwargs['refs'].append('refs/tags/*')
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000902 return git_cache.Mirror(url, **mirror_kwargs)
903
John Budorick882c91e2018-07-12 22:11:41 +0000904 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800905 """Update a git mirror by fetching the latest commits from the remote,
906 unless mirror already contains revision whose type is sha1 hash.
907 """
John Budorick882c91e2018-07-12 22:11:41 +0000908 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800909 if options.verbose:
910 self.Print('skipping mirror update, it has rev=%s already' % revision,
911 timestamp=False)
912 return
913
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000914 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000915 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000916 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000917 depth = 10
918 else:
919 depth = 10000
920 else:
921 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000922 mirror.populate(verbose=options.verbose,
923 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000924 depth=depth,
925 ignore_lock=getattr(options, 'ignore_locks', False),
926 lock_timeout=getattr(options, 'lock_timeout', 0))
927 mirror.unlock()
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000928
John Budorick882c91e2018-07-12 22:11:41 +0000929 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000930 """Clone a git repository from the given URL.
931
msb@chromium.org786fb682010-06-02 15:16:23 +0000932 Once we've cloned the repo, we checkout a working branch if the specified
933 revision is a branch head. If it is a tag or a specific commit, then we
934 leave HEAD detached as it makes future updates simpler -- in this case the
935 user should first create a new branch or switch to an existing branch before
936 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000937 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000938 # git clone doesn't seem to insert a newline properly before printing
939 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000940 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +0000941 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +0000942 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000943 if self.cache_dir:
944 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000945 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000946 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000947 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +0000948 # If the parent directory does not exist, Git clone on Windows will not
949 # create it, so we need to do it manually.
950 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000951 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000952
953 template_dir = None
954 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:41 +0000955 if gclient_utils.IsGitSha(revision):
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000956 # In the case of a subproject, the pinned sha is not necessarily the
957 # head of the remote branch (so we can't just use --depth=N). Instead,
958 # we tell git to fetch all the remote objects from SHA..HEAD by means of
959 # a template git dir which has a 'shallow' file pointing to the sha.
960 template_dir = tempfile.mkdtemp(
961 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
962 dir=parent_dir)
963 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
964 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
965 template_file.write(revision)
966 clone_cmd.append('--template=' + template_dir)
967 else:
968 # Otherwise, we're just interested in the HEAD. Just use --depth.
969 clone_cmd.append('--depth=1')
970
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000971 tmp_dir = tempfile.mkdtemp(
972 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
973 dir=parent_dir)
974 try:
975 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +0100976 if self.print_outbuf:
977 print_stdout = True
978 stdout = gclient_utils.WriteToStdout(self.out_fh)
979 else:
980 print_stdout = False
981 stdout = self.out_fh
982 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
983 print_stdout=print_stdout, stdout=stdout)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000984 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000985 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
986 os.path.join(self.checkout_path, '.git'))
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000987 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000988 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000989 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000990 finally:
991 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000992 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000993 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000994 if template_dir:
995 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:51 +0000996 self._SetFetchConfig(options)
997 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:41 +0000998 revision = self._AutoFetchRef(options, revision)
999 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1000 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +00001001 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +00001002 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001003 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +00001004 ('Checked out %s to a detached HEAD. Before making any commits\n'
1005 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1006 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:41 +00001007 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001008
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001009 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001010 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +00001011 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001012 raise gclient_utils.Error("Background task requires input. Rerun "
1013 "gclient with --jobs=1 so that\n"
1014 "interaction is possible.")
1015 try:
1016 return raw_input(prompt)
1017 except KeyboardInterrupt:
1018 # Hide the exception.
1019 sys.exit(1)
1020
1021
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001022 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001023 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001024 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001025 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -08001026 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001027 revision = upstream
1028 if newbase:
1029 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001030 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001031 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001032 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001033 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001034 printed_path = True
1035 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001036 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001037
1038 if merge:
1039 merge_output = self._Capture(['merge', revision])
1040 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001041 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001042 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001043
1044 # Build the rebase command here using the args
1045 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1046 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001047 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001048 rebase_cmd.append('--verbose')
1049 if newbase:
1050 rebase_cmd.extend(['--onto', newbase])
1051 rebase_cmd.append(upstream)
1052 if branch:
1053 rebase_cmd.append(branch)
1054
1055 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001056 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001057 except subprocess2.CalledProcessError, e:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001058 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
1059 re.match(r'cannot rebase: your index contains uncommitted changes',
1060 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001061 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001062 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001063 'Cannot rebase because of unstaged changes.\n'
1064 '\'git reset --hard HEAD\' ?\n'
1065 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001066 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001067 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001068 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001069 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001070 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001071 break
1072 elif re.match(r'quit|q', rebase_action, re.I):
1073 raise gclient_utils.Error("Please merge or rebase manually\n"
1074 "cd %s && git " % self.checkout_path
1075 + "%s" % ' '.join(rebase_cmd))
1076 elif re.match(r'show|s', rebase_action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001077 self.Print('%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001078 continue
1079 else:
1080 gclient_utils.Error("Input not recognized")
1081 continue
1082 elif re.search(r'^CONFLICT', e.stdout, re.M):
1083 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1084 "Fix the conflict and run gclient again.\n"
1085 "See 'man git-rebase' for details.\n")
1086 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001087 self.Print(e.stdout.strip())
1088 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001089 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1090 "manually.\ncd %s && git " %
1091 self.checkout_path
1092 + "%s" % ' '.join(rebase_cmd))
1093
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001094 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001095 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001096 # Make the output a little prettier. It's nice to have some
1097 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001098 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001099
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001100 @staticmethod
1101 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001102 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1103 if not ok:
1104 raise gclient_utils.Error('git version %s < minimum required %s' %
1105 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001106
John Budorick882c91e2018-07-12 22:11:41 +00001107 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001108 # Special case handling if all 3 conditions are met:
1109 # * the mirros have recently changed, but deps destination remains same,
1110 # * the git histories of mirrors are conflicting.
1111 # * git cache is used
1112 # This manifests itself in current checkout having invalid HEAD commit on
1113 # most git operations. Since git cache is used, just deleted the .git
1114 # folder, and re-create it by cloning.
1115 try:
1116 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1117 except subprocess2.CalledProcessError as e:
1118 if ('fatal: bad object HEAD' in e.stderr
1119 and self.cache_dir and self.cache_dir in url):
1120 self.Print((
1121 'Likely due to DEPS change with git cache_dir, '
1122 'the current commit points to no longer existing object.\n'
1123 '%s' % e)
1124 )
1125 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41 +00001126 self._Clone(revision, url, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001127 else:
1128 raise
1129
msb@chromium.org786fb682010-06-02 15:16:23 +00001130 def _IsRebasing(self):
1131 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1132 # have a plumbing command to determine whether a rebase is in progress, so
1133 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1134 g = os.path.join(self.checkout_path, '.git')
1135 return (
1136 os.path.isdir(os.path.join(g, "rebase-merge")) or
1137 os.path.isdir(os.path.join(g, "rebase-apply")))
1138
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001139 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001140 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1141 if os.path.exists(lockfile):
1142 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001143 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001144 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1145 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001146 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001147
msb@chromium.org786fb682010-06-02 15:16:23 +00001148 # Make sure the tree is clean; see git-rebase.sh for reference
1149 try:
1150 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
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 '\tYou have unstaged 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 try:
1158 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001159 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001160 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001161 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001162 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001163 '\tYour index contains uncommitted changes\n'
1164 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001165 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001166
agable83faed02016-10-24 14:37:10 -07001167 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001168 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1169 # reference by a commit). If not, error out -- most likely a rebase is
1170 # in progress, try to detect so we can give a better error.
1171 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001172 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1173 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001174 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001175 # Commit is not contained by any rev. See if the user is rebasing:
1176 if self._IsRebasing():
1177 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001178 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001179 '\tAlready in a conflict, i.e. (no branch).\n'
1180 '\tFix the conflict and run gclient again.\n'
1181 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1182 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001183 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001184 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001185 name = ('saved-by-gclient-' +
1186 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001187 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001188 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001189 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001190
msb@chromium.org5bde4852009-12-14 16:47:12 +00001191 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001192 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001193 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001194 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001195 return None
1196 return branch
1197
borenet@google.comc3e09d22014-04-10 13:58:18 +00001198 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001199 kwargs.setdefault('cwd', self.checkout_path)
1200 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001201 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001202 env = scm.GIT.ApplyEnvVars(kwargs)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001203 ret = subprocess2.check_output(['git'] + args, env=env, **kwargs)
1204 if strip:
1205 ret = ret.strip()
1206 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001207
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001208 def _Checkout(self, options, ref, force=False, quiet=None):
1209 """Performs a 'git-checkout' operation.
1210
1211 Args:
1212 options: The configured option set
1213 ref: (str) The branch/commit to checkout
1214 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1215 'None', the behavior is inferred from 'options.verbose'.
1216 Returns: (str) The output of the checkout operation
1217 """
1218 if quiet is None:
1219 quiet = (not options.verbose)
1220 checkout_args = ['checkout']
1221 if force:
1222 checkout_args.append('--force')
1223 if quiet:
1224 checkout_args.append('--quiet')
1225 checkout_args.append(ref)
1226 return self._Capture(checkout_args)
1227
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001228 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1229 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001230 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemurd64781e2018-07-11 23:09:55 +00001231 # When a mirror is configured, it fetches only the refs/heads, and possibly
1232 # the refs/branch-heads and refs/tags, but not the refs/changes. So, if
1233 # we're asked to fetch a refs/changes ref from the mirror, it won't have it.
1234 # This makes sure that we always fetch refs/changes directly from the
1235 # repository and not from the mirror.
1236 if refspec and refspec.startswith('refs/changes'):
1237 remote, _ = gclient_utils.SplitUrlRevision(self.url)
1238 # Make sure that we fetch the (remote) refs/changes/xx ref to the (local)
1239 # refs/changes/xx ref.
1240 if ':' not in refspec:
1241 refspec += ':' + refspec
dnj@chromium.org680f2172014-06-25 00:39:32 +00001242 fetch_cmd = cfg + [
1243 'fetch',
1244 remote or self.remote,
1245 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001246 if refspec:
1247 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001248
1249 if prune:
1250 fetch_cmd.append('--prune')
1251 if options.verbose:
1252 fetch_cmd.append('--verbose')
1253 elif quiet:
1254 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001255 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001256
1257 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1258 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1259
Edward Lemur579c9862018-07-13 23:17:51 +00001260 def _SetFetchConfig(self, options):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001261 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1262 if requested."""
Edward Lemur2f38df62018-07-14 02:13:21 +00001263 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:51 +00001264 try:
1265 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1266 options)
1267 self._Run(['config', 'remote.%s.fetch' % self.remote,
1268 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1269 except subprocess2.CalledProcessError as e:
1270 # If exit code was 5, it means we attempted to unset a config that
1271 # didn't exist. Ignore it.
1272 if e.returncode != 5:
1273 raise
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001274 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001275 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001276 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1277 '^\\+refs/branch-heads/\\*:.*$']
1278 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001279 if hasattr(options, 'with_tags') and options.with_tags:
1280 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1281 '+refs/tags/*:refs/tags/*',
1282 '^\\+refs/tags/\\*:.*$']
1283 self._Run(config_cmd, options)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001284
John Budorick882c91e2018-07-12 22:11:41 +00001285 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001286 """Attempts to fetch |revision| if not available in local repo.
1287
1288 Returns possibly updated revision."""
John Budorick882c91e2018-07-12 22:11:41 +00001289 try:
1290 self._Capture(['rev-parse', revision])
1291 except subprocess2.CalledProcessError:
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001292 self._Fetch(options, refspec=revision)
1293 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1294 return revision
1295
dnj@chromium.org680f2172014-06-25 00:39:32 +00001296 def _Run(self, args, options, show_header=True, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001297 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001298 kwargs.setdefault('cwd', self.checkout_path)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001299 kwargs.setdefault('stdout', self.out_fh)
szager@chromium.org848fd492014-04-09 19:06:44 +00001300 kwargs['filter_fn'] = self.filter
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001301 kwargs.setdefault('print_stdout', False)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001302 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001303 cmd = ['git'] + args
dnj@chromium.org680f2172014-06-25 00:39:32 +00001304 if show_header:
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001305 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1306 else:
1307 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001308
1309
1310class CipdPackage(object):
1311 """A representation of a single CIPD package."""
1312
John Budorickd3ba72b2018-03-20 12:27:42 -07001313 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001314 self._authority_for_subdir = authority_for_subdir
1315 self._name = name
1316 self._version = version
1317
1318 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001319 def authority_for_subdir(self):
1320 """Whether this package has authority to act on behalf of its subdir.
1321
1322 Some operations should only be performed once per subdirectory. A package
1323 that has authority for its subdirectory is the only package that should
1324 perform such operations.
1325
1326 Returns:
1327 bool; whether this package has subdir authority.
1328 """
1329 return self._authority_for_subdir
1330
1331 @property
1332 def name(self):
1333 return self._name
1334
1335 @property
1336 def version(self):
1337 return self._version
1338
1339
1340class CipdRoot(object):
1341 """A representation of a single CIPD root."""
1342 def __init__(self, root_dir, service_url):
1343 self._all_packages = set()
1344 self._mutator_lock = threading.Lock()
1345 self._packages_by_subdir = collections.defaultdict(list)
1346 self._root_dir = root_dir
1347 self._service_url = service_url
1348
1349 def add_package(self, subdir, package, version):
1350 """Adds a package to this CIPD root.
1351
1352 As far as clients are concerned, this grants both root and subdir authority
1353 to packages arbitrarily. (The implementation grants root authority to the
1354 first package added and subdir authority to the first package added for that
1355 subdir, but clients should not depend on or expect that behavior.)
1356
1357 Args:
1358 subdir: str; relative path to where the package should be installed from
1359 the cipd root directory.
1360 package: str; the cipd package name.
1361 version: str; the cipd package version.
1362 Returns:
1363 CipdPackage; the package that was created and added to this root.
1364 """
1365 with self._mutator_lock:
1366 cipd_package = CipdPackage(
1367 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001368 not self._packages_by_subdir[subdir])
1369 self._all_packages.add(cipd_package)
1370 self._packages_by_subdir[subdir].append(cipd_package)
1371 return cipd_package
1372
1373 def packages(self, subdir):
1374 """Get the list of configured packages for the given subdir."""
1375 return list(self._packages_by_subdir[subdir])
1376
1377 def clobber(self):
1378 """Remove the .cipd directory.
1379
1380 This is useful for forcing ensure to redownload and reinitialize all
1381 packages.
1382 """
1383 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001384 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001385 try:
1386 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1387 except OSError:
1388 if os.path.exists(cipd_cache_dir):
1389 raise
1390
1391 @contextlib.contextmanager
1392 def _create_ensure_file(self):
1393 try:
1394 ensure_file = None
1395 with tempfile.NamedTemporaryFile(
1396 suffix='.ensure', delete=False) as ensure_file:
John Budorick302bb842018-07-17 23:49:17 +00001397 ensure_file.write('$ParanoidMode CheckPresence\n\n')
John Budorick0f7b2002018-01-19 15:46:17 -08001398 for subdir, packages in sorted(self._packages_by_subdir.iteritems()):
1399 ensure_file.write('@Subdir %s\n' % subdir)
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001400 for package in sorted(packages, key=lambda p: p.name):
John Budorick0f7b2002018-01-19 15:46:17 -08001401 ensure_file.write('%s %s\n' % (package.name, package.version))
1402 ensure_file.write('\n')
1403 yield ensure_file.name
1404 finally:
1405 if ensure_file is not None and os.path.exists(ensure_file.name):
1406 os.remove(ensure_file.name)
1407
1408 def ensure(self):
1409 """Run `cipd ensure`."""
1410 with self._mutator_lock:
1411 with self._create_ensure_file() as ensure_file:
1412 cmd = [
1413 'cipd', 'ensure',
1414 '-log-level', 'error',
1415 '-root', self.root_dir,
1416 '-ensure-file', ensure_file,
1417 ]
1418 gclient_utils.CheckCallAndFilterAndHeader(cmd)
1419
John Budorickd3ba72b2018-03-20 12:27:42 -07001420 def run(self, command):
1421 if command == 'update':
1422 self.ensure()
1423 elif command == 'revert':
1424 self.clobber()
1425 self.ensure()
1426
John Budorick0f7b2002018-01-19 15:46:17 -08001427 def created_package(self, package):
1428 """Checks whether this root created the given package.
1429
1430 Args:
1431 package: CipdPackage; the package to check.
1432 Returns:
1433 bool; whether this root created the given package.
1434 """
1435 return package in self._all_packages
1436
1437 @property
1438 def root_dir(self):
1439 return self._root_dir
1440
1441 @property
1442 def service_url(self):
1443 return self._service_url
1444
1445
1446class CipdWrapper(SCMWrapper):
1447 """Wrapper for CIPD.
1448
1449 Currently only supports chrome-infra-packages.appspot.com.
1450 """
John Budorick3929e9e2018-02-04 18:18:07 -08001451 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001452
1453 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1454 out_cb=None, root=None, package=None):
1455 super(CipdWrapper, self).__init__(
1456 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1457 out_cb=out_cb)
1458 assert root.created_package(package)
1459 self._package = package
1460 self._root = root
1461
1462 #override
1463 def GetCacheMirror(self):
1464 return None
1465
1466 #override
1467 def GetActualRemoteURL(self, options):
1468 return self._root.service_url
1469
1470 #override
1471 def DoesRemoteURLMatch(self, options):
1472 del options
1473 return True
1474
1475 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001476 """Does nothing.
1477
1478 CIPD packages should be reverted at the root by running
1479 `CipdRoot.run('revert')`.
1480 """
1481 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001482
1483 def diff(self, options, args, file_list):
1484 """CIPD has no notion of diffing."""
1485 pass
1486
1487 def pack(self, options, args, file_list):
1488 """CIPD has no notion of diffing."""
1489 pass
1490
1491 def revinfo(self, options, args, file_list):
1492 """Grab the instance ID."""
1493 try:
1494 tmpdir = tempfile.mkdtemp()
1495 describe_json_path = os.path.join(tmpdir, 'describe.json')
1496 cmd = [
1497 'cipd', 'describe',
1498 self._package.name,
1499 '-log-level', 'error',
1500 '-version', self._package.version,
1501 '-json-output', describe_json_path
1502 ]
1503 gclient_utils.CheckCallAndFilter(
1504 cmd, filter_fn=lambda _line: None, print_stdout=False)
1505 with open(describe_json_path) as f:
1506 describe_json = json.load(f)
1507 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1508 finally:
1509 gclient_utils.rmtree(tmpdir)
1510
1511 def status(self, options, args, file_list):
1512 pass
1513
1514 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001515 """Does nothing.
1516
1517 CIPD packages should be updated at the root by running
1518 `CipdRoot.run('update')`.
1519 """
1520 pass