blob: e96d8d1367104f060999d7e9e2502493a5e077b6 [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
306 def _FetchAndReset(self, revision, file_list, options):
307 """Equivalent to git fetch; git reset."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000308 self._UpdateBranchHeads(options, fetch=False)
309
dnj@chromium.org680f2172014-06-25 00:39:32 +0000310 self._Fetch(options, prune=True, quiet=options.verbose)
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800311 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 Lesmesc621b212018-03-21 20:26:56 -0400348 def apply_patch_ref(self, patch_repo, patch_ref, options, file_list):
349 base_rev = self._Capture(['rev-parse', 'HEAD'])
350 self.Print('===Applying patch ref===')
Andrii Shyshkalov690d8d42018-06-14 22:57:17 +0000351 self.Print('Repo is %r @ %r, ref is %r, root is %r' % (
352 patch_repo, patch_ref, base_rev, self.checkout_path))
Edward Lesmesc621b212018-03-21 20:26:56 -0400353 self._Capture(['reset', '--hard'])
354 self._Capture(['fetch', patch_repo, patch_ref])
Andrii Shyshkalov690d8d42018-06-14 22:57:17 +0000355 if file_list is not None:
356 file_list.extend(self._GetDiffFilenames('FETCH_HEAD'))
357 self._Capture(['checkout', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-21 20:26:56 -0400358
Andrii Shyshkalov690d8d42018-06-14 22:57:17 +0000359 if options.rebase_patch_ref:
360 try:
361 # TODO(ehmaldonado): Look into cherry-picking to avoid an expensive
362 # checkout + rebase.
363 self._Capture(['rebase', base_rev])
364 except subprocess2.CalledProcessError as e:
365 self.Print('Failed to apply %r @ %r to %r at %r' % (
366 patch_repo, patch_ref, base_rev, self.checkout_path))
367 self.Print('git returned non-zero exit status %s:\n%s' % (
368 e.returncode, e.stderr))
369 self._Capture(['rebase', '--abort'])
370 raise
Edward Lesmesc621b212018-03-21 20:26:56 -0400371 if options.reset_patch_ref:
372 self._Capture(['reset', '--soft', base_rev])
373
msb@chromium.orge28e4982009-09-25 20:51:45 +0000374 def update(self, options, args, file_list):
375 """Runs git to update or transparently checkout the working copy.
376
377 All updated files will be appended to file_list.
378
379 Raises:
380 Error: if can't get URL for relative path.
381 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000382 if args:
383 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
384
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000385 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000386
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000387 # If a dependency is not pinned, track the default remote branch.
smut@google.comd33eab32014-07-07 19:35:18 +0000388 default_rev = 'refs/remotes/%s/master' % self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000389 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000390 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000391 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000392 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000393 # Override the revision number.
394 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000395 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000396 # Check again for a revision in case an initial ref was specified
397 # in the url, for example bla.git@refs/heads/custombranch
398 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000399 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000400 if not revision:
401 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000402
szager@chromium.org8a139702014-06-20 15:55:01 +0000403 if managed:
404 self._DisableHooks()
405
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000406 printed_path = False
407 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000408 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700409 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000410 verbose = ['--verbose']
411 printed_path = True
412
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000413 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
414 if remote_ref:
smut@google.comd33eab32014-07-07 19:35:18 +0000415 # Rewrite remote refs to their local equivalents.
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000416 revision = ''.join(remote_ref)
417 rev_type = "branch"
418 elif revision.startswith('refs/'):
419 # Local branch? We probably don't want to support, since DEPS should
420 # always specify branches as they are in the upstream repo.
smut@google.comd33eab32014-07-07 19:35:18 +0000421 rev_type = "branch"
422 else:
423 # hash is also a tag, only make a distinction at checkout
424 rev_type = "hash"
425
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000426 mirror = self._GetMirror(url, options)
427 if mirror:
428 url = mirror.mirror_path
429
primiano@chromium.org1c127382015-02-17 11:15:40 +0000430 # If we are going to introduce a new project, there is a possibility that
431 # we are syncing back to a state where the project was originally a
432 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
433 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
434 # In such case, we might have a backup of the former .git folder, which can
435 # be used to avoid re-fetching the entire repo again (useful for bisects).
436 backup_dir = self.GetGitBackupDirPath()
437 target_dir = os.path.join(self.checkout_path, '.git')
438 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
439 gclient_utils.safe_makedirs(self.checkout_path)
440 os.rename(backup_dir, target_dir)
441 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800442 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000443
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000444 if (not os.path.exists(self.checkout_path) or
445 (os.path.isdir(self.checkout_path) and
446 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000447 if mirror:
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800448 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000449 try:
smut@google.comd33eab32014-07-07 19:35:18 +0000450 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000451 except subprocess2.CalledProcessError:
452 self._DeleteOrMove(options.force)
smut@google.comd33eab32014-07-07 19:35:18 +0000453 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000454 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800455 files = self._Capture(
456 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000457 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000458 if not verbose:
459 # Make the output a little prettier. It's nice to have some whitespace
460 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000461 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000462 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000463
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000464 if not managed:
465 self._UpdateBranchHeads(options, fetch=False)
466 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
467 return self._Capture(['rev-parse', '--verify', 'HEAD'])
468
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000469 self._maybe_break_locks(options)
470
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000471 if mirror:
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800472 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000473
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000474 # See if the url has changed (the unittests use git://foo for the url, let
475 # that through).
476 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
477 return_early = False
478 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
479 # unit test pass. (and update the comment above)
480 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
481 # This allows devs to use experimental repos which have a different url
482 # but whose branch(s) are the same as official repos.
borenet@google.comb09097a2014-04-09 19:09:08 +0000483 if (current_url.rstrip('/') != url.rstrip('/') and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000484 url != 'git://foo' and
485 subprocess2.capture(
486 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
487 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000488 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000489 if not (options.force or options.reset):
490 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700491 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000492 # Switch over to the new upstream
493 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000494 if mirror:
495 with open(os.path.join(
496 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
497 'w') as fh:
498 fh.write(os.path.join(url, 'objects'))
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000499 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
smut@google.comd33eab32014-07-07 19:35:18 +0000500 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000501
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000502 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000503 else:
504 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000505
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000506 if return_early:
507 return self._Capture(['rev-parse', '--verify', 'HEAD'])
508
msb@chromium.org5bde4852009-12-14 16:47:12 +0000509 cur_branch = self._GetCurrentBranch()
510
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000511 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000512 # 0) HEAD is detached. Probably from our initial clone.
513 # - make sure HEAD is contained by a named ref, then update.
514 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700515 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000516 # - try to rebase onto the new hash or branch
517 # 2) current branch is tracking a remote branch with local committed
518 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000519 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000520 # 3) current branch is tracking a remote branch w/or w/out changes, and
521 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000522 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000523 # 4) current branch is tracking a remote branch, but DEPS switches to a
524 # different remote branch, and
525 # a) current branch has no local changes, and --force:
526 # - checkout new branch
527 # b) current branch has local changes, and --force and --reset:
528 # - checkout new branch
529 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000530
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000531 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
532 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000533 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
534 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000535 if cur_branch is None:
536 upstream_branch = None
537 current_type = "detached"
538 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000539 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000540 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
541 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
542 current_type = "hash"
543 logging.debug("Current branch is not tracking an upstream (remote)"
544 " branch.")
545 elif upstream_branch.startswith('refs/remotes'):
546 current_type = "branch"
547 else:
548 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000549
smut@google.comd33eab32014-07-07 19:35:18 +0000550 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000551 # Update the remotes first so we have all the refs.
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000552 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000553 cwd=self.checkout_path)
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000554 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000555 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000556
mmoss@chromium.org37ac0e32015-08-18 18:14:38 +0000557 self._UpdateBranchHeads(options, fetch=True)
mmoss@chromium.orge409df62013-04-16 17:28:57 +0000558
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200559 revision = self._AutoFetchRef(options, revision)
560
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000561 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000562 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000563 target = 'HEAD'
564 if options.upstream and upstream_branch:
565 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800566 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000567
msb@chromium.org786fb682010-06-02 15:16:23 +0000568 if current_type == 'detached':
569 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800570 # We just did a Scrub, this is as clean as it's going to get. In
571 # particular if HEAD is a commit that contains two versions of the same
572 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
573 # to actually "Clean" the checkout; that commit is uncheckoutable on this
574 # system. The best we can do is carry forward to the checkout step.
575 if not (options.force or options.reset):
576 self._CheckClean(revision)
agable83faed02016-10-24 14:37:10 -0700577 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000578 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000579 self.Print('Up-to-date; skipping checkout.')
580 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000581 # 'git checkout' may need to overwrite existing untracked files. Allow
582 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000583 self._Checkout(
584 options,
585 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000586 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000587 quiet=True,
588 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000589 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700590 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000591 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000592 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700593 # Can't find a merge-base since we don't know our upstream. That makes
594 # this command VERY likely to produce a rebase failure. For now we
595 # assume origin is our upstream since that's what the old behavior was.
596 upstream_branch = self.remote
597 if options.revision or deps_revision:
598 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700599 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700600 printed_path=printed_path, merge=options.merge)
601 printed_path = True
smut@google.comd33eab32014-07-07 19:35:18 +0000602 elif rev_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000603 # case 2
agable1a8439a2016-10-24 16:36:14 -0700604 self._AttemptRebase(upstream_branch, file_list, options,
smut@google.comd33eab32014-07-07 19:35:18 +0000605 newbase=revision, printed_path=printed_path,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000606 merge=options.merge)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000607 printed_path = True
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000608 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000609 # case 4
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000610 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000611 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700612 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000613 switch_error = ("Could not switch upstream branch from %s to %s\n"
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000614 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000615 "Please use --force or merge or rebase manually:\n" +
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000616 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
617 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000618 force_switch = False
619 if options.force:
620 try:
agable83faed02016-10-24 14:37:10 -0700621 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000622 # case 4a
623 force_switch = True
624 except gclient_utils.Error as e:
625 if options.reset:
626 # case 4b
627 force_switch = True
628 else:
629 switch_error = '%s\n%s' % (e.message, switch_error)
630 if force_switch:
631 self.Print("Switching upstream branch from %s to %s" %
632 (upstream_branch, new_base))
633 switch_branch = 'gclient_' + remote_ref[1]
634 self._Capture(['branch', '-f', switch_branch, new_base])
635 self._Checkout(options, switch_branch, force=True, quiet=True)
636 else:
637 # case 4c
638 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000639 else:
640 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800641 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000642 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000643 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000644 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000645 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000646 if options.merge:
647 merge_args.append('--ff')
648 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000649 merge_args.append('--ff-only')
650 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000651 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000652 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700653 rebase_files = []
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000654 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
655 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700656 self.Print('_____ %s at %s' % (self.relpath, revision),
657 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000658 printed_path = True
659 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000660 if not options.auto_rebase:
661 try:
662 action = self._AskForData(
663 'Cannot %s, attempt to rebase? '
664 '(y)es / (q)uit / (s)kip : ' %
665 ('merge' if options.merge else 'fast-forward merge'),
666 options)
667 except ValueError:
668 raise gclient_utils.Error('Invalid Character')
669 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700670 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000671 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000672 printed_path = True
673 break
674 elif re.match(r'quit|q', action, re.I):
675 raise gclient_utils.Error("Can't fast-forward, please merge or "
676 "rebase manually.\n"
677 "cd %s && git " % self.checkout_path
678 + "rebase %s" % upstream_branch)
679 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000680 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000681 return
682 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000683 self.Print('Input not recognized')
smut@google.com27c9c8a2014-09-11 19:57:55 +0000684 elif re.match("error: Your local changes to '.*' would be "
685 "overwritten by merge. Aborting.\nPlease, commit your "
686 "changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000687 e.stderr):
688 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700689 self.Print('_____ %s at %s' % (self.relpath, revision),
690 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000691 printed_path = True
692 raise gclient_utils.Error(e.stderr)
693 else:
694 # Some other problem happened with the merge
695 logging.error("Error during fast-forward merge in %s!" % self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000696 self.Print(e.stderr)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000697 raise
698 else:
699 # Fast-forward merge was successful
700 if not re.match('Already up-to-date.', merge_output) or verbose:
701 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700702 self.Print('_____ %s at %s' % (self.relpath, revision),
703 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000704 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000705 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000706 if not verbose:
707 # Make the output a little prettier. It's nice to have some
708 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000709 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000710
agablec3937b92016-10-25 10:13:03 -0700711 if file_list is not None:
712 file_list.extend(
713 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000714
715 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000716 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700717 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000718 '\nConflict while rebasing this branch.\n'
719 'Fix the conflict and run gclient again.\n'
720 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700721 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000722
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000723 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000724 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
725 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000726
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000727 # If --reset and --delete_unversioned_trees are specified, remove any
728 # untracked directories.
729 if options.reset and options.delete_unversioned_trees:
730 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
731 # merge-base by default), so doesn't include untracked files. So we use
732 # 'git ls-files --directory --others --exclude-standard' here directly.
733 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800734 ['-c', 'core.quotePath=false', 'ls-files',
735 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000736 self.checkout_path)
737 for path in (p for p in paths.splitlines() if p.endswith('/')):
738 full_path = os.path.join(self.checkout_path, path)
739 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000740 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000741 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000742
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000743 return self._Capture(['rev-parse', '--verify', 'HEAD'])
744
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000745 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000746 """Reverts local modifications.
747
748 All reverted files will be appended to file_list.
749 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000750 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000751 # revert won't work if the directory doesn't exist. It needs to
752 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000753 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000754 # Don't reuse the args.
755 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000756
757 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000758 if options.upstream:
759 if self._GetCurrentBranch():
760 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
761 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000762 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000763 if not deps_revision:
764 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000765 if deps_revision.startswith('refs/heads/'):
766 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700767 try:
768 deps_revision = self.GetUsableRev(deps_revision, options)
769 except NoUsableRevError as e:
770 # If the DEPS entry's url and hash changed, try to update the origin.
771 # See also http://crbug.com/520067.
772 logging.warn(
773 'Couldn\'t find usable revision, will retrying to update instead: %s',
774 e.message)
775 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000776
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000777 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800778 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000779
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800780 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000781 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000782
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000783 if file_list is not None:
784 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
785
786 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000787 """Returns revision"""
788 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000789
msb@chromium.orge28e4982009-09-25 20:51:45 +0000790 def runhooks(self, options, args, file_list):
791 self.status(options, args, file_list)
792
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000793 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000794 """Display status information."""
795 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000796 self.Print('________ couldn\'t run status in %s:\n'
797 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000798 else:
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700799 try:
800 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
801 except subprocess2.CalledProcessError:
802 merge_base = []
Aaron Gablef4068aa2017-12-12 15:14:09 -0800803 self._Run(
804 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
805 options, stdout=self.out_fh, always=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000806 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800807 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000808 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000809
smutae7ea312016-07-18 11:59:41 -0700810 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700811 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700812 sha1 = None
813 if not os.path.isdir(self.checkout_path):
814 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800815 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700816
817 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
818 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700819 else:
agable41e3a6c2016-10-20 11:36:56 -0700820 # May exist in origin, but we don't have it yet, so fetch and look
821 # again.
822 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700823 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
824 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700825
826 if not sha1:
827 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800828 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700829
830 return sha1
831
primiano@chromium.org1c127382015-02-17 11:15:40 +0000832 def GetGitBackupDirPath(self):
833 """Returns the path where the .git folder for the current project can be
834 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
835 return os.path.join(self._root_dir,
836 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
837
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000838 def _GetMirror(self, url, options):
839 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45 +0000840 if not self.cache_dir:
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000841 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000842 mirror_kwargs = {
843 'print_func': self.filter,
844 'refs': []
845 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000846 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
847 mirror_kwargs['refs'].append('refs/branch-heads/*')
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000848 if hasattr(options, 'with_tags') and options.with_tags:
849 mirror_kwargs['refs'].append('refs/tags/*')
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000850 return git_cache.Mirror(url, **mirror_kwargs)
851
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800852 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
853 """Update a git mirror by fetching the latest commits from the remote,
854 unless mirror already contains revision whose type is sha1 hash.
855 """
856 if rev_type == 'hash' and mirror.contains_revision(revision):
857 if options.verbose:
858 self.Print('skipping mirror update, it has rev=%s already' % revision,
859 timestamp=False)
860 return
861
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000862 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000863 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000864 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000865 depth = 10
866 else:
867 depth = 10000
868 else:
869 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000870 mirror.populate(verbose=options.verbose,
871 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000872 depth=depth,
873 ignore_lock=getattr(options, 'ignore_locks', False),
874 lock_timeout=getattr(options, 'lock_timeout', 0))
875 mirror.unlock()
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000876
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000877 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000878 """Clone a git repository from the given URL.
879
msb@chromium.org786fb682010-06-02 15:16:23 +0000880 Once we've cloned the repo, we checkout a working branch if the specified
881 revision is a branch head. If it is a tag or a specific commit, then we
882 leave HEAD detached as it makes future updates simpler -- in this case the
883 user should first create a new branch or switch to an existing branch before
884 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000885 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000886 # git clone doesn't seem to insert a newline properly before printing
887 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000888 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +0000889 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +0000890 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000891 if self.cache_dir:
892 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000893 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000894 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000895 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +0000896 # If the parent directory does not exist, Git clone on Windows will not
897 # create it, so we need to do it manually.
898 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000899 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000900
901 template_dir = None
902 if hasattr(options, 'no_history') and options.no_history:
903 if gclient_utils.IsGitSha(revision):
904 # In the case of a subproject, the pinned sha is not necessarily the
905 # head of the remote branch (so we can't just use --depth=N). Instead,
906 # we tell git to fetch all the remote objects from SHA..HEAD by means of
907 # a template git dir which has a 'shallow' file pointing to the sha.
908 template_dir = tempfile.mkdtemp(
909 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
910 dir=parent_dir)
911 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
912 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
913 template_file.write(revision)
914 clone_cmd.append('--template=' + template_dir)
915 else:
916 # Otherwise, we're just interested in the HEAD. Just use --depth.
917 clone_cmd.append('--depth=1')
918
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000919 tmp_dir = tempfile.mkdtemp(
920 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
921 dir=parent_dir)
922 try:
923 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 19:02:36 +0100924 if self.print_outbuf:
925 print_stdout = True
926 stdout = gclient_utils.WriteToStdout(self.out_fh)
927 else:
928 print_stdout = False
929 stdout = self.out_fh
930 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
931 print_stdout=print_stdout, stdout=stdout)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000932 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000933 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
934 os.path.join(self.checkout_path, '.git'))
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000935 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000936 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000937 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000938 finally:
939 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000940 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000941 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000942 if template_dir:
943 gclient_utils.rmtree(template_dir)
mmoss@chromium.org1a6bec02014-06-02 21:53:29 +0000944 self._UpdateBranchHeads(options, fetch=True)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200945 revision = self._AutoFetchRef(options, revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000946 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
947 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000948 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +0000949 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000950 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +0000951 ('Checked out %s to a detached HEAD. Before making any commits\n'
952 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
953 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
954 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000955
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000956 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000957 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000958 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000959 raise gclient_utils.Error("Background task requires input. Rerun "
960 "gclient with --jobs=1 so that\n"
961 "interaction is possible.")
962 try:
963 return raw_input(prompt)
964 except KeyboardInterrupt:
965 # Hide the exception.
966 sys.exit(1)
967
968
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000969 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000970 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000971 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000972 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800973 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000974 revision = upstream
975 if newbase:
976 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000977 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000978 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000979 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000980 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000981 printed_path = True
982 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000983 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000984
985 if merge:
986 merge_output = self._Capture(['merge', revision])
987 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000988 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000989 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000990
991 # Build the rebase command here using the args
992 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
993 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000994 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000995 rebase_cmd.append('--verbose')
996 if newbase:
997 rebase_cmd.extend(['--onto', newbase])
998 rebase_cmd.append(upstream)
999 if branch:
1000 rebase_cmd.append(branch)
1001
1002 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001003 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001004 except subprocess2.CalledProcessError, e:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001005 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
1006 re.match(r'cannot rebase: your index contains uncommitted changes',
1007 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001008 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +00001009 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +00001010 'Cannot rebase because of unstaged changes.\n'
1011 '\'git reset --hard HEAD\' ?\n'
1012 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +00001013 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001014 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001015 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001016 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001017 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001018 break
1019 elif re.match(r'quit|q', rebase_action, re.I):
1020 raise gclient_utils.Error("Please merge or rebase manually\n"
1021 "cd %s && git " % self.checkout_path
1022 + "%s" % ' '.join(rebase_cmd))
1023 elif re.match(r'show|s', rebase_action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001024 self.Print('%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001025 continue
1026 else:
1027 gclient_utils.Error("Input not recognized")
1028 continue
1029 elif re.search(r'^CONFLICT', e.stdout, re.M):
1030 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1031 "Fix the conflict and run gclient again.\n"
1032 "See 'man git-rebase' for details.\n")
1033 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001034 self.Print(e.stdout.strip())
1035 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001036 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1037 "manually.\ncd %s && git " %
1038 self.checkout_path
1039 + "%s" % ' '.join(rebase_cmd))
1040
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001041 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001042 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001043 # Make the output a little prettier. It's nice to have some
1044 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001045 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001046
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001047 @staticmethod
1048 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001049 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1050 if not ok:
1051 raise gclient_utils.Error('git version %s < minimum required %s' %
1052 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001053
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001054 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
1055 # Special case handling if all 3 conditions are met:
1056 # * the mirros have recently changed, but deps destination remains same,
1057 # * the git histories of mirrors are conflicting.
1058 # * git cache is used
1059 # This manifests itself in current checkout having invalid HEAD commit on
1060 # most git operations. Since git cache is used, just deleted the .git
1061 # folder, and re-create it by cloning.
1062 try:
1063 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1064 except subprocess2.CalledProcessError as e:
1065 if ('fatal: bad object HEAD' in e.stderr
1066 and self.cache_dir and self.cache_dir in url):
1067 self.Print((
1068 'Likely due to DEPS change with git cache_dir, '
1069 'the current commit points to no longer existing object.\n'
1070 '%s' % e)
1071 )
1072 self._DeleteOrMove(options.force)
1073 self._Clone(revision, url, options)
1074 else:
1075 raise
1076
msb@chromium.org786fb682010-06-02 15:16:23 +00001077 def _IsRebasing(self):
1078 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1079 # have a plumbing command to determine whether a rebase is in progress, so
1080 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1081 g = os.path.join(self.checkout_path, '.git')
1082 return (
1083 os.path.isdir(os.path.join(g, "rebase-merge")) or
1084 os.path.isdir(os.path.join(g, "rebase-apply")))
1085
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001086 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001087 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1088 if os.path.exists(lockfile):
1089 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001090 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001091 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1092 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001093 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001094
msb@chromium.org786fb682010-06-02 15:16:23 +00001095 # Make sure the tree is clean; see git-rebase.sh for reference
1096 try:
1097 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001098 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001099 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001100 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001101 '\tYou have unstaged changes.\n'
1102 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001103 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001104 try:
1105 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001106 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001107 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001108 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001109 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001110 '\tYour index contains uncommitted changes\n'
1111 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001112 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001113
agable83faed02016-10-24 14:37:10 -07001114 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001115 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1116 # reference by a commit). If not, error out -- most likely a rebase is
1117 # in progress, try to detect so we can give a better error.
1118 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001119 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1120 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001121 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001122 # Commit is not contained by any rev. See if the user is rebasing:
1123 if self._IsRebasing():
1124 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001125 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001126 '\tAlready in a conflict, i.e. (no branch).\n'
1127 '\tFix the conflict and run gclient again.\n'
1128 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1129 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001130 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001131 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001132 name = ('saved-by-gclient-' +
1133 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001134 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001135 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001136 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001137
msb@chromium.org5bde4852009-12-14 16:47:12 +00001138 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001139 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001140 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001141 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001142 return None
1143 return branch
1144
borenet@google.comc3e09d22014-04-10 13:58:18 +00001145 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001146 kwargs.setdefault('cwd', self.checkout_path)
1147 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001148 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001149 env = scm.GIT.ApplyEnvVars(kwargs)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001150 ret = subprocess2.check_output(['git'] + args, env=env, **kwargs)
1151 if strip:
1152 ret = ret.strip()
1153 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001154
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001155 def _Checkout(self, options, ref, force=False, quiet=None):
1156 """Performs a 'git-checkout' operation.
1157
1158 Args:
1159 options: The configured option set
1160 ref: (str) The branch/commit to checkout
1161 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1162 'None', the behavior is inferred from 'options.verbose'.
1163 Returns: (str) The output of the checkout operation
1164 """
1165 if quiet is None:
1166 quiet = (not options.verbose)
1167 checkout_args = ['checkout']
1168 if force:
1169 checkout_args.append('--force')
1170 if quiet:
1171 checkout_args.append('--quiet')
1172 checkout_args.append(ref)
1173 return self._Capture(checkout_args)
1174
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001175 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1176 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001177 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemurd64781e2018-07-11 23:09:55 +00001178 # When a mirror is configured, it fetches only the refs/heads, and possibly
1179 # the refs/branch-heads and refs/tags, but not the refs/changes. So, if
1180 # we're asked to fetch a refs/changes ref from the mirror, it won't have it.
1181 # This makes sure that we always fetch refs/changes directly from the
1182 # repository and not from the mirror.
1183 if refspec and refspec.startswith('refs/changes'):
1184 remote, _ = gclient_utils.SplitUrlRevision(self.url)
1185 # Make sure that we fetch the (remote) refs/changes/xx ref to the (local)
1186 # refs/changes/xx ref.
1187 if ':' not in refspec:
1188 refspec += ':' + refspec
dnj@chromium.org680f2172014-06-25 00:39:32 +00001189 fetch_cmd = cfg + [
1190 'fetch',
1191 remote or self.remote,
1192 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001193 if refspec:
1194 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001195
1196 if prune:
1197 fetch_cmd.append('--prune')
1198 if options.verbose:
1199 fetch_cmd.append('--verbose')
1200 elif quiet:
1201 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001202 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001203
1204 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1205 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1206
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001207 def _UpdateBranchHeads(self, options, fetch=False):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001208 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1209 if requested."""
1210 need_fetch = fetch
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001211 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001212 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001213 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1214 '^\\+refs/branch-heads/\\*:.*$']
1215 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001216 need_fetch = True
1217 if hasattr(options, 'with_tags') and options.with_tags:
1218 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1219 '+refs/tags/*:refs/tags/*',
1220 '^\\+refs/tags/\\*:.*$']
1221 self._Run(config_cmd, options)
1222 need_fetch = True
1223 if fetch and need_fetch:
bpastene2a3e9912016-09-07 16:22:25 -07001224 self._Fetch(options, prune=options.force)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001225
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001226 def _AutoFetchRef(self, options, revision):
1227 """Attempts to fetch |revision| if not available in local repo.
1228
1229 Returns possibly updated revision."""
1230 try:
1231 self._Capture(['rev-parse', revision])
1232 except subprocess2.CalledProcessError:
1233 self._Fetch(options, refspec=revision)
1234 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1235 return revision
1236
dnj@chromium.org680f2172014-06-25 00:39:32 +00001237 def _Run(self, args, options, show_header=True, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001238 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001239 kwargs.setdefault('cwd', self.checkout_path)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001240 kwargs.setdefault('stdout', self.out_fh)
szager@chromium.org848fd492014-04-09 19:06:44 +00001241 kwargs['filter_fn'] = self.filter
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001242 kwargs.setdefault('print_stdout', False)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001243 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001244 cmd = ['git'] + args
dnj@chromium.org680f2172014-06-25 00:39:32 +00001245 if show_header:
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001246 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1247 else:
1248 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001249
1250
1251class CipdPackage(object):
1252 """A representation of a single CIPD package."""
1253
John Budorickd3ba72b2018-03-20 12:27:42 -07001254 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 15:46:17 -08001255 self._authority_for_subdir = authority_for_subdir
1256 self._name = name
1257 self._version = version
1258
1259 @property
John Budorick0f7b2002018-01-19 15:46:17 -08001260 def authority_for_subdir(self):
1261 """Whether this package has authority to act on behalf of its subdir.
1262
1263 Some operations should only be performed once per subdirectory. A package
1264 that has authority for its subdirectory is the only package that should
1265 perform such operations.
1266
1267 Returns:
1268 bool; whether this package has subdir authority.
1269 """
1270 return self._authority_for_subdir
1271
1272 @property
1273 def name(self):
1274 return self._name
1275
1276 @property
1277 def version(self):
1278 return self._version
1279
1280
1281class CipdRoot(object):
1282 """A representation of a single CIPD root."""
1283 def __init__(self, root_dir, service_url):
1284 self._all_packages = set()
1285 self._mutator_lock = threading.Lock()
1286 self._packages_by_subdir = collections.defaultdict(list)
1287 self._root_dir = root_dir
1288 self._service_url = service_url
1289
1290 def add_package(self, subdir, package, version):
1291 """Adds a package to this CIPD root.
1292
1293 As far as clients are concerned, this grants both root and subdir authority
1294 to packages arbitrarily. (The implementation grants root authority to the
1295 first package added and subdir authority to the first package added for that
1296 subdir, but clients should not depend on or expect that behavior.)
1297
1298 Args:
1299 subdir: str; relative path to where the package should be installed from
1300 the cipd root directory.
1301 package: str; the cipd package name.
1302 version: str; the cipd package version.
1303 Returns:
1304 CipdPackage; the package that was created and added to this root.
1305 """
1306 with self._mutator_lock:
1307 cipd_package = CipdPackage(
1308 package, version,
John Budorick0f7b2002018-01-19 15:46:17 -08001309 not self._packages_by_subdir[subdir])
1310 self._all_packages.add(cipd_package)
1311 self._packages_by_subdir[subdir].append(cipd_package)
1312 return cipd_package
1313
1314 def packages(self, subdir):
1315 """Get the list of configured packages for the given subdir."""
1316 return list(self._packages_by_subdir[subdir])
1317
1318 def clobber(self):
1319 """Remove the .cipd directory.
1320
1321 This is useful for forcing ensure to redownload and reinitialize all
1322 packages.
1323 """
1324 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 12:27:42 -07001325 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 15:46:17 -08001326 try:
1327 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1328 except OSError:
1329 if os.path.exists(cipd_cache_dir):
1330 raise
1331
1332 @contextlib.contextmanager
1333 def _create_ensure_file(self):
1334 try:
1335 ensure_file = None
1336 with tempfile.NamedTemporaryFile(
1337 suffix='.ensure', delete=False) as ensure_file:
1338 for subdir, packages in sorted(self._packages_by_subdir.iteritems()):
1339 ensure_file.write('@Subdir %s\n' % subdir)
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001340 for package in sorted(packages, key=lambda p: p.name):
John Budorick0f7b2002018-01-19 15:46:17 -08001341 ensure_file.write('%s %s\n' % (package.name, package.version))
1342 ensure_file.write('\n')
1343 yield ensure_file.name
1344 finally:
1345 if ensure_file is not None and os.path.exists(ensure_file.name):
1346 os.remove(ensure_file.name)
1347
1348 def ensure(self):
1349 """Run `cipd ensure`."""
1350 with self._mutator_lock:
1351 with self._create_ensure_file() as ensure_file:
1352 cmd = [
1353 'cipd', 'ensure',
1354 '-log-level', 'error',
1355 '-root', self.root_dir,
1356 '-ensure-file', ensure_file,
1357 ]
1358 gclient_utils.CheckCallAndFilterAndHeader(cmd)
1359
John Budorickd3ba72b2018-03-20 12:27:42 -07001360 def run(self, command):
1361 if command == 'update':
1362 self.ensure()
1363 elif command == 'revert':
1364 self.clobber()
1365 self.ensure()
1366
John Budorick0f7b2002018-01-19 15:46:17 -08001367 def created_package(self, package):
1368 """Checks whether this root created the given package.
1369
1370 Args:
1371 package: CipdPackage; the package to check.
1372 Returns:
1373 bool; whether this root created the given package.
1374 """
1375 return package in self._all_packages
1376
1377 @property
1378 def root_dir(self):
1379 return self._root_dir
1380
1381 @property
1382 def service_url(self):
1383 return self._service_url
1384
1385
1386class CipdWrapper(SCMWrapper):
1387 """Wrapper for CIPD.
1388
1389 Currently only supports chrome-infra-packages.appspot.com.
1390 """
John Budorick3929e9e2018-02-04 18:18:07 -08001391 name = 'cipd'
John Budorick0f7b2002018-01-19 15:46:17 -08001392
1393 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1394 out_cb=None, root=None, package=None):
1395 super(CipdWrapper, self).__init__(
1396 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1397 out_cb=out_cb)
1398 assert root.created_package(package)
1399 self._package = package
1400 self._root = root
1401
1402 #override
1403 def GetCacheMirror(self):
1404 return None
1405
1406 #override
1407 def GetActualRemoteURL(self, options):
1408 return self._root.service_url
1409
1410 #override
1411 def DoesRemoteURLMatch(self, options):
1412 del options
1413 return True
1414
1415 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001416 """Does nothing.
1417
1418 CIPD packages should be reverted at the root by running
1419 `CipdRoot.run('revert')`.
1420 """
1421 pass
John Budorick0f7b2002018-01-19 15:46:17 -08001422
1423 def diff(self, options, args, file_list):
1424 """CIPD has no notion of diffing."""
1425 pass
1426
1427 def pack(self, options, args, file_list):
1428 """CIPD has no notion of diffing."""
1429 pass
1430
1431 def revinfo(self, options, args, file_list):
1432 """Grab the instance ID."""
1433 try:
1434 tmpdir = tempfile.mkdtemp()
1435 describe_json_path = os.path.join(tmpdir, 'describe.json')
1436 cmd = [
1437 'cipd', 'describe',
1438 self._package.name,
1439 '-log-level', 'error',
1440 '-version', self._package.version,
1441 '-json-output', describe_json_path
1442 ]
1443 gclient_utils.CheckCallAndFilter(
1444 cmd, filter_fn=lambda _line: None, print_stdout=False)
1445 with open(describe_json_path) as f:
1446 describe_json = json.load(f)
1447 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1448 finally:
1449 gclient_utils.rmtree(tmpdir)
1450
1451 def status(self, options, args, file_list):
1452 pass
1453
1454 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 12:27:42 -07001455 """Does nothing.
1456
1457 CIPD packages should be updated at the root by running
1458 `CipdRoot.run('update')`.
1459 """
1460 pass