blob: 50cfd79776b2a2f6580f98ec960fc9220705f5ed [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,
97 out_cb=None):
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
111
112 def Print(self, *args, **kwargs):
113 kwargs.setdefault('file', self.out_fh)
114 if kwargs.pop('timestamp', True):
115 self.out_fh.write('[%s] ' % gclient_utils.Elapsed())
116 print(*args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000117
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000118 def RunCommand(self, command, options, args, file_list=None):
agabledebf6c82016-12-21 12:50:12 -0800119 commands = ['update', 'updatesingle', 'revert',
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000120 'revinfo', 'status', 'diff', 'pack', 'runhooks']
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000121
122 if not command in commands:
123 raise gclient_utils.Error('Unknown command %s' % command)
124
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000125 if not command in dir(self):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000126 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000127 command, self.__class__.__name__))
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000128
129 return getattr(self, command)(options, args, file_list)
130
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000131 @staticmethod
132 def _get_first_remote_url(checkout_path):
133 log = scm.GIT.Capture(
134 ['config', '--local', '--get-regexp', r'remote.*.url'],
135 cwd=checkout_path)
136 # Get the second token of the first line of the log.
137 return log.splitlines()[0].split(' ', 1)[1]
138
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000139 def GetCacheMirror(self):
140 if (getattr(self, 'cache_dir', None)):
141 url, _ = gclient_utils.SplitUrlRevision(self.url)
142 return git_cache.Mirror(url)
143 return None
144
smut@google.comd33eab32014-07-07 19:35:18 +0000145 def GetActualRemoteURL(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000146 """Attempt to determine the remote URL for this SCMWrapper."""
smut@google.comd33eab32014-07-07 19:35:18 +0000147 # Git
borenet@google.combda475e2014-03-24 19:04:45 +0000148 if os.path.exists(os.path.join(self.checkout_path, '.git')):
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +0000149 actual_remote_url = self._get_first_remote_url(self.checkout_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000150
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +0000151 mirror = self.GetCacheMirror()
152 # If the cache is used, obtain the actual remote URL from there.
153 if (mirror and mirror.exists() and
154 mirror.mirror_path.replace('\\', '/') ==
155 actual_remote_url.replace('\\', '/')):
156 actual_remote_url = self._get_first_remote_url(mirror.mirror_path)
borenet@google.com4e9be262014-04-08 19:40:30 +0000157 return actual_remote_url
borenet@google.com88d10082014-03-21 17:24:48 +0000158 return None
159
borenet@google.com4e9be262014-04-08 19:40:30 +0000160 def DoesRemoteURLMatch(self, options):
borenet@google.com88d10082014-03-21 17:24:48 +0000161 """Determine whether the remote URL of this checkout is the expected URL."""
162 if not os.path.exists(self.checkout_path):
163 # A checkout which doesn't exist can't be broken.
164 return True
165
smut@google.comd33eab32014-07-07 19:35:18 +0000166 actual_remote_url = self.GetActualRemoteURL(options)
borenet@google.com88d10082014-03-21 17:24:48 +0000167 if actual_remote_url:
borenet@google.com8156c9f2014-04-01 16:41:36 +0000168 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
169 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
borenet@google.com88d10082014-03-21 17:24:48 +0000170 else:
171 # This may occur if the self.checkout_path exists but does not contain a
agable41e3a6c2016-10-20 11:36:56 -0700172 # valid git checkout.
borenet@google.com88d10082014-03-21 17:24:48 +0000173 return False
174
borenet@google.comb09097a2014-04-09 19:09:08 +0000175 def _DeleteOrMove(self, force):
176 """Delete the checkout directory or move it out of the way.
177
178 Args:
179 force: bool; if True, delete the directory. Otherwise, just move it.
180 """
borenet@google.comb2256212014-05-07 20:57:28 +0000181 if force and os.environ.get('CHROME_HEADLESS') == '1':
182 self.Print('_____ Conflicting directory found in %s. Removing.'
183 % self.checkout_path)
184 gclient_utils.AddWarning('Conflicting directory %s deleted.'
185 % self.checkout_path)
186 gclient_utils.rmtree(self.checkout_path)
187 else:
188 bad_scm_dir = os.path.join(self._root_dir, '_bad_scm',
189 os.path.dirname(self.relpath))
190
191 try:
192 os.makedirs(bad_scm_dir)
193 except OSError as e:
194 if e.errno != errno.EEXIST:
195 raise
196
197 dest_path = tempfile.mkdtemp(
198 prefix=os.path.basename(self.relpath),
199 dir=bad_scm_dir)
200 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
201 % (self.checkout_path, dest_path))
202 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
203 % (self.checkout_path, dest_path))
204 shutil.move(self.checkout_path, dest_path)
borenet@google.comb09097a2014-04-09 19:09:08 +0000205
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000206
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000207class GitWrapper(SCMWrapper):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000208 """Wrapper for Git"""
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000209 name = 'git'
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000210 remote = 'origin'
msb@chromium.orge28e4982009-09-25 20:51:45 +0000211
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000212 cache_dir = None
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000213
John Budorick0f7b2002018-01-19 15:46:17 -0800214 def __init__(self, url=None, *args, **kwargs):
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000215 """Removes 'git+' fake prefix from git URL."""
216 if url.startswith('git+http://') or url.startswith('git+https://'):
217 url = url[4:]
John Budorick0f7b2002018-01-19 15:46:17 -0800218 SCMWrapper.__init__(self, url, *args, **kwargs)
szager@chromium.org848fd492014-04-09 19:06:44 +0000219 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
220 if self.out_cb:
221 filter_kwargs['predicate'] = self.out_cb
222 self.filter = gclient_utils.GitFilter(**filter_kwargs)
igorgatis@gmail.com4e075672011-11-21 16:35:08 +0000223
mukai@chromium.org9e3e82c2012-04-18 12:55:43 +0000224 @staticmethod
225 def BinaryExists():
226 """Returns true if the command exists."""
227 try:
228 # We assume git is newer than 1.7. See: crbug.com/114483
229 result, version = scm.GIT.AssertVersion('1.7')
230 if not result:
231 raise gclient_utils.Error('Git version is older than 1.7: %s' % version)
232 return result
233 except OSError:
234 return False
235
xusydoc@chromium.org885a9602013-05-31 09:54:40 +0000236 def GetCheckoutRoot(self):
237 return scm.GIT.GetCheckoutRoot(self.checkout_path)
238
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000239 def GetRevisionDate(self, _revision):
floitsch@google.comeaab7842011-04-28 09:07:58 +0000240 """Returns the given revision's date in ISO-8601 format (which contains the
241 time zone)."""
242 # TODO(floitsch): get the time-stamp of the given revision and not just the
243 # time-stamp of the currently checked out revision.
244 return self._Capture(['log', '-n', '1', '--format=%ai'])
245
Aaron Gablef4068aa2017-12-12 15:14:09 -0800246 def _GetDiffFilenames(self, base):
247 """Returns the names of files modified since base."""
248 return self._Capture(
249 # Filter to remove base if it is None.
250 filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only', base])
251 ).split()
252
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000253 def diff(self, options, _args, _file_list):
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700254 try:
255 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
256 except subprocess2.CalledProcessError:
257 merge_base = []
Aaron Gablef4068aa2017-12-12 15:14:09 -0800258 self._Run(['-c', 'core.quotePath=false', 'diff'] + merge_base, options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000259
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000260 def pack(self, _options, _args, _file_list):
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000261 """Generates a patch file which can be applied to the root of the
msb@chromium.orgd6504212010-01-13 17:34:31 +0000262 repository.
263
264 The patch file is generated from a diff of the merge base of HEAD and
265 its upstream branch.
266 """
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700267 try:
268 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
269 except subprocess2.CalledProcessError:
270 merge_base = []
maruel@chromium.org17d01792010-09-01 18:07:10 +0000271 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700272 ['git', 'diff'] + merge_base,
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000273 cwd=self.checkout_path,
avakulenko@google.com255f2be2014-12-05 22:19:55 +0000274 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
maruel@chromium.orgee4071d2009-12-22 22:25:37 +0000275
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800276 def _Scrub(self, target, options):
277 """Scrubs out all changes in the local repo, back to the state of target."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000278 quiet = []
279 if not options.verbose:
280 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800281 self._Run(['reset', '--hard', target] + quiet, options)
282 if options.force and options.delete_unversioned_trees:
283 # where `target` is a commit that contains both upper and lower case
284 # versions of the same file on a case insensitive filesystem, we are
285 # actually in a broken state here. The index will have both 'a' and 'A',
286 # but only one of them will exist on the disk. To progress, we delete
287 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 09:43:17 -0800288 output = self._Capture([
289 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800290 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800291 # --porcelain (v1) looks like:
292 # XY filename
293 try:
294 filename = line[3:]
295 self.Print('_____ Deleting residual after reset: %r.' % filename)
296 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -0800297 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800298 except OSError:
299 pass
300
301 def _FetchAndReset(self, revision, file_list, options):
302 """Equivalent to git fetch; git reset."""
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000303 self._UpdateBranchHeads(options, fetch=False)
304
dnj@chromium.org680f2172014-06-25 00:39:32 +0000305 self._Fetch(options, prune=True, quiet=options.verbose)
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800306 self._Scrub(revision, options)
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000307 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800308 files = self._Capture(
309 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000310 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
311
szager@chromium.org8a139702014-06-20 15:55:01 +0000312 def _DisableHooks(self):
313 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
314 if not os.path.isdir(hook_dir):
315 return
316 for f in os.listdir(hook_dir):
317 if not f.endswith('.sample') and not f.endswith('.disabled'):
primiano@chromium.org41265562015-04-08 09:14:46 +0000318 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
319 if os.path.exists(disabled_hook_path):
320 os.remove(disabled_hook_path)
321 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
szager@chromium.org8a139702014-06-20 15:55:01 +0000322
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000323 def _maybe_break_locks(self, options):
324 """This removes all .lock files from this repo's .git directory, if the
325 user passed the --break_repo_locks command line flag.
326
327 In particular, this will cleanup index.lock files, as well as ref lock
328 files.
329 """
330 if options.break_repo_locks:
331 git_dir = os.path.join(self.checkout_path, '.git')
332 for path, _, filenames in os.walk(git_dir):
333 for filename in filenames:
334 if filename.endswith('.lock'):
335 to_break = os.path.join(path, filename)
336 self.Print('breaking lock: %s' % (to_break,))
337 try:
338 os.remove(to_break)
339 except OSError as ex:
340 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
341 raise
342
msb@chromium.orge28e4982009-09-25 20:51:45 +0000343 def update(self, options, args, file_list):
344 """Runs git to update or transparently checkout the working copy.
345
346 All updated files will be appended to file_list.
347
348 Raises:
349 Error: if can't get URL for relative path.
350 """
msb@chromium.orge28e4982009-09-25 20:51:45 +0000351 if args:
352 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
353
nasser@codeaurora.orgece406f2010-02-23 17:29:15 +0000354 self._CheckMinVersion("1.6.6")
msb@chromium.org923a0372009-12-11 20:42:43 +0000355
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000356 # If a dependency is not pinned, track the default remote branch.
smut@google.comd33eab32014-07-07 19:35:18 +0000357 default_rev = 'refs/remotes/%s/master' % self.remote
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000358 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.org7080e942010-03-15 15:06:16 +0000359 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000360 managed = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000361 if options.revision:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000362 # Override the revision number.
363 revision = str(options.revision)
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000364 if revision == 'unmanaged':
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000365 # Check again for a revision in case an initial ref was specified
366 # in the url, for example bla.git@refs/heads/custombranch
367 revision = deps_revision
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000368 managed = False
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000369 if not revision:
370 revision = default_rev
msb@chromium.orge28e4982009-09-25 20:51:45 +0000371
szager@chromium.org8a139702014-06-20 15:55:01 +0000372 if managed:
373 self._DisableHooks()
374
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000375 printed_path = False
376 verbose = []
msb@chromium.orgb1a22bf2009-11-07 02:33:50 +0000377 if options.verbose:
agable83faed02016-10-24 14:37:10 -0700378 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000379 verbose = ['--verbose']
380 printed_path = True
381
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000382 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
383 if remote_ref:
smut@google.comd33eab32014-07-07 19:35:18 +0000384 # Rewrite remote refs to their local equivalents.
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000385 revision = ''.join(remote_ref)
386 rev_type = "branch"
387 elif revision.startswith('refs/'):
388 # Local branch? We probably don't want to support, since DEPS should
389 # always specify branches as they are in the upstream repo.
smut@google.comd33eab32014-07-07 19:35:18 +0000390 rev_type = "branch"
391 else:
392 # hash is also a tag, only make a distinction at checkout
393 rev_type = "hash"
394
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000395 mirror = self._GetMirror(url, options)
396 if mirror:
397 url = mirror.mirror_path
398
primiano@chromium.org1c127382015-02-17 11:15:40 +0000399 # If we are going to introduce a new project, there is a possibility that
400 # we are syncing back to a state where the project was originally a
401 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
402 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
403 # In such case, we might have a backup of the former .git folder, which can
404 # be used to avoid re-fetching the entire repo again (useful for bisects).
405 backup_dir = self.GetGitBackupDirPath()
406 target_dir = os.path.join(self.checkout_path, '.git')
407 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
408 gclient_utils.safe_makedirs(self.checkout_path)
409 os.rename(backup_dir, target_dir)
410 # Reset to a clean state
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800411 self._Scrub('HEAD', options)
primiano@chromium.org1c127382015-02-17 11:15:40 +0000412
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000413 if (not os.path.exists(self.checkout_path) or
414 (os.path.isdir(self.checkout_path) and
415 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000416 if mirror:
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800417 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000418 try:
smut@google.comd33eab32014-07-07 19:35:18 +0000419 self._Clone(revision, url, options)
borenet@google.com90fe58b2014-05-01 18:22:00 +0000420 except subprocess2.CalledProcessError:
421 self._DeleteOrMove(options.force)
smut@google.comd33eab32014-07-07 19:35:18 +0000422 self._Clone(revision, url, options)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000423 if file_list is not None:
Aaron Gable7817f022017-12-12 09:43:17 -0800424 files = self._Capture(
425 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000426 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000427 if not verbose:
428 # Make the output a little prettier. It's nice to have some whitespace
429 # between projects when cloning.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000430 self.Print('')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000431 return self._Capture(['rev-parse', '--verify', 'HEAD'])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000432
szager@chromium.org3dc5cb72014-06-17 15:06:05 +0000433 if not managed:
434 self._UpdateBranchHeads(options, fetch=False)
435 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
436 return self._Capture(['rev-parse', '--verify', 'HEAD'])
437
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000438 self._maybe_break_locks(options)
439
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000440 if mirror:
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800441 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000442
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000443 # See if the url has changed (the unittests use git://foo for the url, let
444 # that through).
445 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
446 return_early = False
447 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
448 # unit test pass. (and update the comment above)
449 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
450 # This allows devs to use experimental repos which have a different url
451 # but whose branch(s) are the same as official repos.
borenet@google.comb09097a2014-04-09 19:09:08 +0000452 if (current_url.rstrip('/') != url.rstrip('/') and
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000453 url != 'git://foo' and
454 subprocess2.capture(
455 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
456 cwd=self.checkout_path).strip() != 'False'):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000457 self.Print('_____ switching %s to a new upstream' % self.relpath)
iannucci@chromium.org78514212014-08-20 23:08:00 +0000458 if not (options.force or options.reset):
459 # Make sure it's clean
agable83faed02016-10-24 14:37:10 -0700460 self._CheckClean(revision)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000461 # Switch over to the new upstream
462 self._Run(['remote', 'set-url', self.remote, url], options)
szager@chromium.org8dd35462015-06-08 22:56:05 +0000463 if mirror:
464 with open(os.path.join(
465 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
466 'w') as fh:
467 fh.write(os.path.join(url, 'objects'))
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000468 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
smut@google.comd33eab32014-07-07 19:35:18 +0000469 self._FetchAndReset(revision, file_list, options)
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000470
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000471 return_early = True
tandrii@chromium.orgc438c142015-08-24 22:55:55 +0000472 else:
473 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
szager@chromium.org6c2b49d2014-02-26 23:57:38 +0000474
mmoss@chromium.org50fd47f2014-02-13 01:03:19 +0000475 if return_early:
476 return self._Capture(['rev-parse', '--verify', 'HEAD'])
477
msb@chromium.org5bde4852009-12-14 16:47:12 +0000478 cur_branch = self._GetCurrentBranch()
479
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000480 # Cases:
msb@chromium.org786fb682010-06-02 15:16:23 +0000481 # 0) HEAD is detached. Probably from our initial clone.
482 # - make sure HEAD is contained by a named ref, then update.
483 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 11:36:56 -0700484 # 1) current branch is not tracking a remote branch
msb@chromium.org786fb682010-06-02 15:16:23 +0000485 # - try to rebase onto the new hash or branch
486 # 2) current branch is tracking a remote branch with local committed
487 # changes, but the DEPS file switched to point to a hash
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000488 # - rebase those changes on top of the hash
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000489 # 3) current branch is tracking a remote branch w/or w/out changes, and
490 # no DEPS switch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000491 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000492 # 4) current branch is tracking a remote branch, but DEPS switches to a
493 # different remote branch, and
494 # a) current branch has no local changes, and --force:
495 # - checkout new branch
496 # b) current branch has local changes, and --force and --reset:
497 # - checkout new branch
498 # c) otherwise exit
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000499
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000500 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
501 # a tracking branch
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000502 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
503 # or it returns None if it couldn't find an upstream
msb@chromium.org786fb682010-06-02 15:16:23 +0000504 if cur_branch is None:
505 upstream_branch = None
506 current_type = "detached"
507 logging.debug("Detached HEAD")
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000508 else:
msb@chromium.org786fb682010-06-02 15:16:23 +0000509 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
510 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
511 current_type = "hash"
512 logging.debug("Current branch is not tracking an upstream (remote)"
513 " branch.")
514 elif upstream_branch.startswith('refs/remotes'):
515 current_type = "branch"
516 else:
517 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000518
smut@google.comd33eab32014-07-07 19:35:18 +0000519 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000520 # Update the remotes first so we have all the refs.
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000521 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000522 cwd=self.checkout_path)
bauerb@chromium.orgcbd20a42012-06-27 13:49:27 +0000523 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000524 self.Print(remote_output)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000525
mmoss@chromium.org37ac0e32015-08-18 18:14:38 +0000526 self._UpdateBranchHeads(options, fetch=True)
mmoss@chromium.orge409df62013-04-16 17:28:57 +0000527
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200528 revision = self._AutoFetchRef(options, revision)
529
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000530 # This is a big hammer, debatable if it should even be here...
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000531 if options.force or options.reset:
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000532 target = 'HEAD'
533 if options.upstream and upstream_branch:
534 target = upstream_branch
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800535 self._Scrub(target, options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000536
msb@chromium.org786fb682010-06-02 15:16:23 +0000537 if current_type == 'detached':
538 # case 0
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800539 # We just did a Scrub, this is as clean as it's going to get. In
540 # particular if HEAD is a commit that contains two versions of the same
541 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
542 # to actually "Clean" the checkout; that commit is uncheckoutable on this
543 # system. The best we can do is carry forward to the checkout step.
544 if not (options.force or options.reset):
545 self._CheckClean(revision)
agable83faed02016-10-24 14:37:10 -0700546 self._CheckDetachedHead(revision, options)
smut@google.comd33eab32014-07-07 19:35:18 +0000547 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
szager@chromium.org848fd492014-04-09 19:06:44 +0000548 self.Print('Up-to-date; skipping checkout.')
549 else:
vadimsh@chromium.org2b7d3ed2014-06-20 18:15:37 +0000550 # 'git checkout' may need to overwrite existing untracked files. Allow
551 # it only when nuclear options are enabled.
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000552 self._Checkout(
553 options,
554 revision,
smut@google.com34b4e982016-05-16 19:06:07 +0000555 force=(options.force and options.delete_unversioned_trees),
dnj@chromium.orgbb424c02014-06-23 22:42:51 +0000556 quiet=True,
557 )
msb@chromium.org786fb682010-06-02 15:16:23 +0000558 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700559 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
msb@chromium.org786fb682010-06-02 15:16:23 +0000560 elif current_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000561 # case 1
agable41e3a6c2016-10-20 11:36:56 -0700562 # Can't find a merge-base since we don't know our upstream. That makes
563 # this command VERY likely to produce a rebase failure. For now we
564 # assume origin is our upstream since that's what the old behavior was.
565 upstream_branch = self.remote
566 if options.revision or deps_revision:
567 upstream_branch = revision
agable1a8439a2016-10-24 16:36:14 -0700568 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 11:36:56 -0700569 printed_path=printed_path, merge=options.merge)
570 printed_path = True
smut@google.comd33eab32014-07-07 19:35:18 +0000571 elif rev_type == 'hash':
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000572 # case 2
agable1a8439a2016-10-24 16:36:14 -0700573 self._AttemptRebase(upstream_branch, file_list, options,
smut@google.comd33eab32014-07-07 19:35:18 +0000574 newbase=revision, printed_path=printed_path,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000575 merge=options.merge)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000576 printed_path = True
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000577 elif remote_ref and ''.join(remote_ref) != upstream_branch:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000578 # case 4
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000579 new_base = ''.join(remote_ref)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000580 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700581 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000582 switch_error = ("Could not switch upstream branch from %s to %s\n"
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000583 % (upstream_branch, new_base) +
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000584 "Please use --force or merge or rebase manually:\n" +
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000585 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
586 "OR git checkout -b <some new branch> %s" % new_base)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000587 force_switch = False
588 if options.force:
589 try:
agable83faed02016-10-24 14:37:10 -0700590 self._CheckClean(revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000591 # case 4a
592 force_switch = True
593 except gclient_utils.Error as e:
594 if options.reset:
595 # case 4b
596 force_switch = True
597 else:
598 switch_error = '%s\n%s' % (e.message, switch_error)
599 if force_switch:
600 self.Print("Switching upstream branch from %s to %s" %
601 (upstream_branch, new_base))
602 switch_branch = 'gclient_' + remote_ref[1]
603 self._Capture(['branch', '-f', switch_branch, new_base])
604 self._Checkout(options, switch_branch, force=True, quiet=True)
605 else:
606 # case 4c
607 raise gclient_utils.Error(switch_error)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000608 else:
609 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 15:14:09 -0800610 rebase_files = self._GetDiffFilenames(upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000611 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000612 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000613 try:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000614 merge_args = ['merge']
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000615 if options.merge:
616 merge_args.append('--ff')
617 else:
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +0000618 merge_args.append('--ff-only')
619 merge_args.append(upstream_branch)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000620 merge_output = self._Capture(merge_args)
bratell@opera.com18fa4542013-05-21 13:30:46 +0000621 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 16:36:14 -0700622 rebase_files = []
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000623 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
624 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700625 self.Print('_____ %s at %s' % (self.relpath, revision),
626 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000627 printed_path = True
628 while True:
dnj@chromium.org5b23e872015-02-20 21:25:57 +0000629 if not options.auto_rebase:
630 try:
631 action = self._AskForData(
632 'Cannot %s, attempt to rebase? '
633 '(y)es / (q)uit / (s)kip : ' %
634 ('merge' if options.merge else 'fast-forward merge'),
635 options)
636 except ValueError:
637 raise gclient_utils.Error('Invalid Character')
638 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 16:36:14 -0700639 self._AttemptRebase(upstream_branch, rebase_files, options,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000640 printed_path=printed_path, merge=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000641 printed_path = True
642 break
643 elif re.match(r'quit|q', action, re.I):
644 raise gclient_utils.Error("Can't fast-forward, please merge or "
645 "rebase manually.\n"
646 "cd %s && git " % self.checkout_path
647 + "rebase %s" % upstream_branch)
648 elif re.match(r'skip|s', action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000649 self.Print('Skipping %s' % self.relpath)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000650 return
651 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000652 self.Print('Input not recognized')
smut@google.com27c9c8a2014-09-11 19:57:55 +0000653 elif re.match("error: Your local changes to '.*' would be "
654 "overwritten by merge. Aborting.\nPlease, commit your "
655 "changes or stash them before you can merge.\n",
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000656 e.stderr):
657 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700658 self.Print('_____ %s at %s' % (self.relpath, revision),
659 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000660 printed_path = True
661 raise gclient_utils.Error(e.stderr)
662 else:
663 # Some other problem happened with the merge
664 logging.error("Error during fast-forward merge in %s!" % self.relpath)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000665 self.Print(e.stderr)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000666 raise
667 else:
668 # Fast-forward merge was successful
669 if not re.match('Already up-to-date.', merge_output) or verbose:
670 if not printed_path:
agable83faed02016-10-24 14:37:10 -0700671 self.Print('_____ %s at %s' % (self.relpath, revision),
672 timestamp=False)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000673 printed_path = True
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000674 self.Print(merge_output.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000675 if not verbose:
676 # Make the output a little prettier. It's nice to have some
677 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000678 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000679
agablec3937b92016-10-25 10:13:03 -0700680 if file_list is not None:
681 file_list.extend(
682 [os.path.join(self.checkout_path, f) for f in rebase_files])
msb@chromium.org5bde4852009-12-14 16:47:12 +0000683
684 # If the rebase generated a conflict, abort and ask user to fix
msb@chromium.org786fb682010-06-02 15:16:23 +0000685 if self._IsRebasing():
agable83faed02016-10-24 14:37:10 -0700686 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org5bde4852009-12-14 16:47:12 +0000687 '\nConflict while rebasing this branch.\n'
688 'Fix the conflict and run gclient again.\n'
689 'See man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -0700690 % (self.relpath, revision))
msb@chromium.org5bde4852009-12-14 16:47:12 +0000691
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000692 if verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000693 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
694 timestamp=False)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000695
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000696 # If --reset and --delete_unversioned_trees are specified, remove any
697 # untracked directories.
698 if options.reset and options.delete_unversioned_trees:
699 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
700 # merge-base by default), so doesn't include untracked files. So we use
701 # 'git ls-files --directory --others --exclude-standard' here directly.
702 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 09:43:17 -0800703 ['-c', 'core.quotePath=false', 'ls-files',
704 '--directory', '--others', '--exclude-standard'],
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000705 self.checkout_path)
706 for path in (p for p in paths.splitlines() if p.endswith('/')):
707 full_path = os.path.join(self.checkout_path, path)
708 if not os.path.islink(full_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000709 self.Print('_____ removing unversioned directory %s' % path)
digit@chromium.orgdc112ac2013-04-24 13:00:19 +0000710 gclient_utils.rmtree(full_path)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000711
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000712 return self._Capture(['rev-parse', '--verify', 'HEAD'])
713
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000714 def revert(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000715 """Reverts local modifications.
716
717 All reverted files will be appended to file_list.
718 """
maruel@chromium.org8469bf92010-09-03 19:03:15 +0000719 if not os.path.isdir(self.checkout_path):
msb@chromium.org260c6532009-10-28 03:22:35 +0000720 # revert won't work if the directory doesn't exist. It needs to
721 # checkout instead.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000722 self.Print('_____ %s is missing, synching instead' % self.relpath)
msb@chromium.org260c6532009-10-28 03:22:35 +0000723 # Don't reuse the args.
724 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000725
726 default_rev = "refs/heads/master"
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +0000727 if options.upstream:
728 if self._GetCurrentBranch():
729 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
730 default_rev = upstream_branch or default_rev
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000731 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000732 if not deps_revision:
733 deps_revision = default_rev
smut@google.comd33eab32014-07-07 19:35:18 +0000734 if deps_revision.startswith('refs/heads/'):
735 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 11:59:41 -0700736 try:
737 deps_revision = self.GetUsableRev(deps_revision, options)
738 except NoUsableRevError as e:
739 # If the DEPS entry's url and hash changed, try to update the origin.
740 # See also http://crbug.com/520067.
741 logging.warn(
742 'Couldn\'t find usable revision, will retrying to update instead: %s',
743 e.message)
744 return self.update(options, [], file_list)
nasser@codeaurora.orgb2b46312010-04-30 20:58:03 +0000745
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000746 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800747 files = self._GetDiffFilenames(deps_revision)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000748
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800749 self._Scrub(deps_revision, options)
lliabraa@chromium.orgade83db2012-09-27 14:06:49 +0000750 self._Run(['clean', '-f', '-d'], options)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000751
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000752 if file_list is not None:
753 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
754
755 def revinfo(self, _options, _args, _file_list):
maruel@chromium.org6cafa132010-09-07 14:17:26 +0000756 """Returns revision"""
757 return self._Capture(['rev-parse', 'HEAD'])
msb@chromium.org0f282062009-11-06 20:14:02 +0000758
msb@chromium.orge28e4982009-09-25 20:51:45 +0000759 def runhooks(self, options, args, file_list):
760 self.status(options, args, file_list)
761
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000762 def status(self, options, _args, file_list):
msb@chromium.orge28e4982009-09-25 20:51:45 +0000763 """Display status information."""
764 if not os.path.isdir(self.checkout_path):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000765 self.Print('________ couldn\'t run status in %s:\n'
766 'The directory does not exist.' % self.checkout_path)
msb@chromium.orge28e4982009-09-25 20:51:45 +0000767 else:
raphael.kubo.da.costa05c83592016-08-04 08:32:41 -0700768 try:
769 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
770 except subprocess2.CalledProcessError:
771 merge_base = []
Aaron Gablef4068aa2017-12-12 15:14:09 -0800772 self._Run(
773 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
774 options, stdout=self.out_fh, always=options.verbose)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000775 if file_list is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800776 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000777 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
msb@chromium.orge28e4982009-09-25 20:51:45 +0000778
smutae7ea312016-07-18 11:59:41 -0700779 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 11:36:56 -0700780 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 11:59:41 -0700781 sha1 = None
782 if not os.path.isdir(self.checkout_path):
783 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800784 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 11:36:56 -0700785
786 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
787 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700788 else:
agable41e3a6c2016-10-20 11:36:56 -0700789 # May exist in origin, but we don't have it yet, so fetch and look
790 # again.
791 self._Fetch(options)
smutae7ea312016-07-18 11:59:41 -0700792 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
793 sha1 = rev
smutae7ea312016-07-18 11:59:41 -0700794
795 if not sha1:
796 raise NoUsableRevError(
agablea98a6cd2016-11-15 14:30:10 -0800797 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 11:59:41 -0700798
799 return sha1
800
msb@chromium.orge6f78352010-01-13 17:05:33 +0000801 def FullUrlForRelativeUrl(self, url):
802 # Strip from last '/'
803 # Equivalent to unix basename
804 base_url = self.url
805 return base_url[:base_url.rfind('/')] + url
806
primiano@chromium.org1c127382015-02-17 11:15:40 +0000807 def GetGitBackupDirPath(self):
808 """Returns the path where the .git folder for the current project can be
809 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
810 return os.path.join(self._root_dir,
811 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
812
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000813 def _GetMirror(self, url, options):
814 """Get a git_cache.Mirror object for the argument url."""
815 if not git_cache.Mirror.GetCachePath():
816 return None
hinoka@google.comb1b54572014-04-16 22:29:23 +0000817 mirror_kwargs = {
818 'print_func': self.filter,
819 'refs': []
820 }
hinoka@google.comb1b54572014-04-16 22:29:23 +0000821 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
822 mirror_kwargs['refs'].append('refs/branch-heads/*')
szager@chromium.org8d3348f2014-08-19 22:49:16 +0000823 if hasattr(options, 'with_tags') and options.with_tags:
824 mirror_kwargs['refs'].append('refs/tags/*')
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000825 return git_cache.Mirror(url, **mirror_kwargs)
826
Andrii Shyshkalov46a672b2017-11-24 18:04:43 -0800827 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
828 """Update a git mirror by fetching the latest commits from the remote,
829 unless mirror already contains revision whose type is sha1 hash.
830 """
831 if rev_type == 'hash' and mirror.contains_revision(revision):
832 if options.verbose:
833 self.Print('skipping mirror update, it has rev=%s already' % revision,
834 timestamp=False)
835 return
836
szager@chromium.org3ec84f62014-08-22 21:00:22 +0000837 if getattr(options, 'shallow', False):
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000838 # HACK(hinoka): These repositories should be super shallow.
szager@chromium.orgb0a13a22014-06-18 00:52:25 +0000839 if 'flash' in mirror.url:
hinoka@chromium.org46b87412014-05-15 00:42:05 +0000840 depth = 10
841 else:
842 depth = 10000
843 else:
844 depth = None
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +0000845 mirror.populate(verbose=options.verbose,
846 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52 +0000847 depth=depth,
848 ignore_lock=getattr(options, 'ignore_locks', False),
849 lock_timeout=getattr(options, 'lock_timeout', 0))
850 mirror.unlock()
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000851
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000852 def _Clone(self, revision, url, options):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000853 """Clone a git repository from the given URL.
854
msb@chromium.org786fb682010-06-02 15:16:23 +0000855 Once we've cloned the repo, we checkout a working branch if the specified
856 revision is a branch head. If it is a tag or a specific commit, then we
857 leave HEAD detached as it makes future updates simpler -- in this case the
858 user should first create a new branch or switch to an existing branch before
859 making changes in the repo."""
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000860 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000861 # git clone doesn't seem to insert a newline properly before printing
862 # to stdout
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000863 self.Print('')
szager@chromium.orgf1d73eb2014-04-21 17:07:04 +0000864 cfg = gclient_utils.DefaultIndexPackConfig(url)
szager@chromium.org8a139702014-06-20 15:55:01 +0000865 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000866 if self.cache_dir:
867 clone_cmd.append('--shared')
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000868 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000869 clone_cmd.append('--verbose')
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000870 clone_cmd.append(url)
nsylvain@chromium.org328c3c72011-06-01 20:50:27 +0000871 # If the parent directory does not exist, Git clone on Windows will not
872 # create it, so we need to do it manually.
873 parent_dir = os.path.dirname(self.checkout_path)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000874 gclient_utils.safe_makedirs(parent_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000875
876 template_dir = None
877 if hasattr(options, 'no_history') and options.no_history:
878 if gclient_utils.IsGitSha(revision):
879 # In the case of a subproject, the pinned sha is not necessarily the
880 # head of the remote branch (so we can't just use --depth=N). Instead,
881 # we tell git to fetch all the remote objects from SHA..HEAD by means of
882 # a template git dir which has a 'shallow' file pointing to the sha.
883 template_dir = tempfile.mkdtemp(
884 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
885 dir=parent_dir)
886 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
887 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
888 template_file.write(revision)
889 clone_cmd.append('--template=' + template_dir)
890 else:
891 # Otherwise, we're just interested in the HEAD. Just use --depth.
892 clone_cmd.append('--depth=1')
893
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000894 tmp_dir = tempfile.mkdtemp(
895 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
896 dir=parent_dir)
897 try:
898 clone_cmd.append(tmp_dir)
agable@chromium.orgfd5b6382013-10-25 20:54:34 +0000899 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000900 gclient_utils.safe_makedirs(self.checkout_path)
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000901 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
902 os.path.join(self.checkout_path, '.git'))
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000903 except:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000904 traceback.print_exc(file=self.out_fh)
zty@chromium.org6279e8a2014-02-13 01:45:25 +0000905 raise
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000906 finally:
907 if os.listdir(tmp_dir):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000908 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
ilevy@chromium.org3534aa52013-07-20 01:58:08 +0000909 gclient_utils.rmtree(tmp_dir)
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000910 if template_dir:
911 gclient_utils.rmtree(template_dir)
mmoss@chromium.org1a6bec02014-06-02 21:53:29 +0000912 self._UpdateBranchHeads(options, fetch=True)
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +0200913 revision = self._AutoFetchRef(options, revision)
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000914 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
915 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000916 if self._GetCurrentBranch() is None:
msb@chromium.org786fb682010-06-02 15:16:23 +0000917 # Squelch git's very verbose detached HEAD warning and use our own
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000918 self.Print(
smut@google.comd33eab32014-07-07 19:35:18 +0000919 ('Checked out %s to a detached HEAD. Before making any commits\n'
920 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
921 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
922 'create a new branch for your work.') % (revision, self.remote))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000923
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000924 def _AskForData(self, prompt, options):
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000925 if options.jobs > 1:
szager@chromium.org6cd41b62014-04-21 23:55:22 +0000926 self.Print(prompt)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000927 raise gclient_utils.Error("Background task requires input. Rerun "
928 "gclient with --jobs=1 so that\n"
929 "interaction is possible.")
930 try:
931 return raw_input(prompt)
932 except KeyboardInterrupt:
933 # Hide the exception.
934 sys.exit(1)
935
936
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000937 def _AttemptRebase(self, upstream, files, options, newbase=None,
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000938 branch=None, printed_path=False, merge=False):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000939 """Attempt to rebase onto either upstream or, if specified, newbase."""
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000940 if files is not None:
Aaron Gablef4068aa2017-12-12 15:14:09 -0800941 files.extend(self._GetDiffFilenames(upstream))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000942 revision = upstream
943 if newbase:
944 revision = newbase
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000945 action = 'merge' if merge else 'rebase'
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000946 if not printed_path:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000947 self.Print('_____ %s : Attempting %s onto %s...' % (
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000948 self.relpath, action, revision))
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000949 printed_path = True
950 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000951 self.Print('Attempting %s onto %s...' % (action, revision))
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000952
953 if merge:
954 merge_output = self._Capture(['merge', revision])
955 if options.verbose:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000956 self.Print(merge_output)
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000957 return
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000958
959 # Build the rebase command here using the args
960 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
961 rebase_cmd = ['rebase']
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +0000962 if options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000963 rebase_cmd.append('--verbose')
964 if newbase:
965 rebase_cmd.extend(['--onto', newbase])
966 rebase_cmd.append(upstream)
967 if branch:
968 rebase_cmd.append(branch)
969
970 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000971 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +0000972 except subprocess2.CalledProcessError, e:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000973 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
974 re.match(r'cannot rebase: your index contains uncommitted changes',
975 e.stderr)):
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000976 while True:
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000977 rebase_action = self._AskForData(
maruel@chromium.org90541732011-04-01 17:54:18 +0000978 'Cannot rebase because of unstaged changes.\n'
979 '\'git reset --hard HEAD\' ?\n'
980 'WARNING: destroys any uncommitted work in your current branch!'
bratell@opera.com18fa4542013-05-21 13:30:46 +0000981 ' (y)es / (q)uit / (s)how : ', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000982 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-16 17:07:37 -0800983 self._Scrub('HEAD', options)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000984 # Should this be recursive?
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000985 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000986 break
987 elif re.match(r'quit|q', rebase_action, re.I):
988 raise gclient_utils.Error("Please merge or rebase manually\n"
989 "cd %s && git " % self.checkout_path
990 + "%s" % ' '.join(rebase_cmd))
991 elif re.match(r'show|s', rebase_action, re.I):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000992 self.Print('%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000993 continue
994 else:
995 gclient_utils.Error("Input not recognized")
996 continue
997 elif re.search(r'^CONFLICT', e.stdout, re.M):
998 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
999 "Fix the conflict and run gclient again.\n"
1000 "See 'man git-rebase' for details.\n")
1001 else:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001002 self.Print(e.stdout.strip())
1003 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001004 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1005 "manually.\ncd %s && git " %
1006 self.checkout_path
1007 + "%s" % ' '.join(rebase_cmd))
1008
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001009 self.Print(rebase_output.strip())
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001010 if not options.verbose:
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001011 # Make the output a little prettier. It's nice to have some
1012 # whitespace between projects when syncing.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001013 self.Print('')
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +00001014
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001015 @staticmethod
1016 def _CheckMinVersion(min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +00001017 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1018 if not ok:
1019 raise gclient_utils.Error('git version %s < minimum required %s' %
1020 (current_version, min_version))
msb@chromium.org923a0372009-12-11 20:42:43 +00001021
tandrii@chromium.orgc438c142015-08-24 22:55:55 +00001022 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
1023 # Special case handling if all 3 conditions are met:
1024 # * the mirros have recently changed, but deps destination remains same,
1025 # * the git histories of mirrors are conflicting.
1026 # * git cache is used
1027 # This manifests itself in current checkout having invalid HEAD commit on
1028 # most git operations. Since git cache is used, just deleted the .git
1029 # folder, and re-create it by cloning.
1030 try:
1031 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1032 except subprocess2.CalledProcessError as e:
1033 if ('fatal: bad object HEAD' in e.stderr
1034 and self.cache_dir and self.cache_dir in url):
1035 self.Print((
1036 'Likely due to DEPS change with git cache_dir, '
1037 'the current commit points to no longer existing object.\n'
1038 '%s' % e)
1039 )
1040 self._DeleteOrMove(options.force)
1041 self._Clone(revision, url, options)
1042 else:
1043 raise
1044
msb@chromium.org786fb682010-06-02 15:16:23 +00001045 def _IsRebasing(self):
1046 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1047 # have a plumbing command to determine whether a rebase is in progress, so
1048 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1049 g = os.path.join(self.checkout_path, '.git')
1050 return (
1051 os.path.isdir(os.path.join(g, "rebase-merge")) or
1052 os.path.isdir(os.path.join(g, "rebase-apply")))
1053
Robert Iannuccic41d8b92017-02-16 17:07:37 -08001054 def _CheckClean(self, revision, fixup=False):
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001055 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1056 if os.path.exists(lockfile):
1057 raise gclient_utils.Error(
agable83faed02016-10-24 14:37:10 -07001058 '\n____ %s at %s\n'
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001059 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1060 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 14:37:10 -07001061 % (self.relpath, revision, lockfile))
iannucci@chromium.orgd9b318c2015-12-04 20:03:08 +00001062
msb@chromium.org786fb682010-06-02 15:16:23 +00001063 # Make sure the tree is clean; see git-rebase.sh for reference
1064 try:
1065 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001066 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001067 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001068 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001069 '\tYou have unstaged changes.\n'
1070 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001071 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001072 try:
1073 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
smut@google.com27c9c8a2014-09-11 19:57:55 +00001074 '--ignore-submodules', 'HEAD', '--'],
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001075 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001076 except subprocess2.CalledProcessError:
agable83faed02016-10-24 14:37:10 -07001077 raise gclient_utils.Error('\n____ %s at %s\n'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001078 '\tYour index contains uncommitted changes\n'
1079 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 14:37:10 -07001080 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001081
agable83faed02016-10-24 14:37:10 -07001082 def _CheckDetachedHead(self, revision, _options):
msb@chromium.org786fb682010-06-02 15:16:23 +00001083 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1084 # reference by a commit). If not, error out -- most likely a rebase is
1085 # in progress, try to detect so we can give a better error.
1086 try:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +00001087 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1088 cwd=self.checkout_path)
maruel@chromium.orgbffad372011-09-08 17:54:22 +00001089 except subprocess2.CalledProcessError:
msb@chromium.org786fb682010-06-02 15:16:23 +00001090 # Commit is not contained by any rev. See if the user is rebasing:
1091 if self._IsRebasing():
1092 # Punt to the user
agable83faed02016-10-24 14:37:10 -07001093 raise gclient_utils.Error('\n____ %s at %s\n'
msb@chromium.org786fb682010-06-02 15:16:23 +00001094 '\tAlready in a conflict, i.e. (no branch).\n'
1095 '\tFix the conflict and run gclient again.\n'
1096 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1097 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 14:37:10 -07001098 % (self.relpath, revision))
msb@chromium.org786fb682010-06-02 15:16:23 +00001099 # Let's just save off the commit so we can proceed.
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001100 name = ('saved-by-gclient-' +
1101 self._Capture(['rev-parse', '--short', 'HEAD']))
mmoss@chromium.org77bd7362013-09-25 23:46:14 +00001102 self._Capture(['branch', '-f', name])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001103 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
maruel@chromium.orgf5d37bf2010-09-02 00:50:34 +00001104 name)
msb@chromium.org786fb682010-06-02 15:16:23 +00001105
msb@chromium.org5bde4852009-12-14 16:47:12 +00001106 def _GetCurrentBranch(self):
msb@chromium.org786fb682010-06-02 15:16:23 +00001107 # Returns name of current branch or None for detached HEAD
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001108 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
msb@chromium.org786fb682010-06-02 15:16:23 +00001109 if branch == 'HEAD':
msb@chromium.org5bde4852009-12-14 16:47:12 +00001110 return None
1111 return branch
1112
borenet@google.comc3e09d22014-04-10 13:58:18 +00001113 def _Capture(self, args, **kwargs):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001114 kwargs.setdefault('cwd', self.checkout_path)
1115 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001116 strip = kwargs.pop('strip', True)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001117 env = scm.GIT.ApplyEnvVars(kwargs)
Robert Iannuccia7a9ceb2017-02-16 17:38:06 -08001118 ret = subprocess2.check_output(['git'] + args, env=env, **kwargs)
1119 if strip:
1120 ret = ret.strip()
1121 return ret
maruel@chromium.org6cafa132010-09-07 14:17:26 +00001122
dnj@chromium.orgbb424c02014-06-23 22:42:51 +00001123 def _Checkout(self, options, ref, force=False, quiet=None):
1124 """Performs a 'git-checkout' operation.
1125
1126 Args:
1127 options: The configured option set
1128 ref: (str) The branch/commit to checkout
1129 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1130 'None', the behavior is inferred from 'options.verbose'.
1131 Returns: (str) The output of the checkout operation
1132 """
1133 if quiet is None:
1134 quiet = (not options.verbose)
1135 checkout_args = ['checkout']
1136 if force:
1137 checkout_args.append('--force')
1138 if quiet:
1139 checkout_args.append('--quiet')
1140 checkout_args.append(ref)
1141 return self._Capture(checkout_args)
1142
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001143 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1144 refspec=None):
dnj@chromium.org680f2172014-06-25 00:39:32 +00001145 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
1146 fetch_cmd = cfg + [
1147 'fetch',
1148 remote or self.remote,
1149 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001150 if refspec:
1151 fetch_cmd.append(refspec)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001152
1153 if prune:
1154 fetch_cmd.append('--prune')
1155 if options.verbose:
1156 fetch_cmd.append('--verbose')
1157 elif quiet:
1158 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 05:30:05 -07001159 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
dnj@chromium.org680f2172014-06-25 00:39:32 +00001160
1161 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1162 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1163
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001164 def _UpdateBranchHeads(self, options, fetch=False):
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001165 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1166 if requested."""
1167 need_fetch = fetch
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001168 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
maruel@chromium.org1a60dca2013-11-26 14:06:26 +00001169 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +00001170 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1171 '^\\+refs/branch-heads/\\*:.*$']
1172 self._Run(config_cmd, options)
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001173 need_fetch = True
1174 if hasattr(options, 'with_tags') and options.with_tags:
1175 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1176 '+refs/tags/*:refs/tags/*',
1177 '^\\+refs/tags/\\*:.*$']
1178 self._Run(config_cmd, options)
1179 need_fetch = True
1180 if fetch and need_fetch:
bpastene2a3e9912016-09-07 16:22:25 -07001181 self._Fetch(options, prune=options.force)
mmoss@chromium.orge409df62013-04-16 17:28:57 +00001182
Paweł Hajdan, Jr63b8c2a2017-09-05 17:59:08 +02001183 def _AutoFetchRef(self, options, revision):
1184 """Attempts to fetch |revision| if not available in local repo.
1185
1186 Returns possibly updated revision."""
1187 try:
1188 self._Capture(['rev-parse', revision])
1189 except subprocess2.CalledProcessError:
1190 self._Fetch(options, refspec=revision)
1191 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1192 return revision
1193
dnj@chromium.org680f2172014-06-25 00:39:32 +00001194 def _Run(self, args, options, show_header=True, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001195 # Disable 'unused options' warning | pylint: disable=unused-argument
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001196 kwargs.setdefault('cwd', self.checkout_path)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001197 kwargs.setdefault('stdout', self.out_fh)
szager@chromium.org848fd492014-04-09 19:06:44 +00001198 kwargs['filter_fn'] = self.filter
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001199 kwargs.setdefault('print_stdout', False)
szager@chromium.org6d8115d2014-04-23 20:59:23 +00001200 env = scm.GIT.ApplyEnvVars(kwargs)
agable@chromium.org772efaf2014-04-01 02:35:44 +00001201 cmd = ['git'] + args
dnj@chromium.org680f2172014-06-25 00:39:32 +00001202 if show_header:
sbc@chromium.org2cd0b8e2014-09-22 21:17:59 +00001203 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1204 else:
1205 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 15:46:17 -08001206
1207
1208class CipdPackage(object):
1209 """A representation of a single CIPD package."""
1210
1211 def __init__(self, name, version, authority_for_root, authority_for_subdir):
1212 self._authority_for_root = authority_for_root
1213 self._authority_for_subdir = authority_for_subdir
1214 self._name = name
1215 self._version = version
1216
1217 @property
1218 def authority_for_root(self):
1219 """Whether this package has authority to act on behalf of its root.
1220
1221 Some operations should only be performed once per cipd root. A package
1222 that has authority for its cipd root is the only package that should
1223 perform such operations.
1224
1225 Returns:
1226 bool; whether this package has root authority.
1227 """
1228 return self._authority_for_root
1229
1230 @property
1231 def authority_for_subdir(self):
1232 """Whether this package has authority to act on behalf of its subdir.
1233
1234 Some operations should only be performed once per subdirectory. A package
1235 that has authority for its subdirectory is the only package that should
1236 perform such operations.
1237
1238 Returns:
1239 bool; whether this package has subdir authority.
1240 """
1241 return self._authority_for_subdir
1242
1243 @property
1244 def name(self):
1245 return self._name
1246
1247 @property
1248 def version(self):
1249 return self._version
1250
1251
1252class CipdRoot(object):
1253 """A representation of a single CIPD root."""
1254 def __init__(self, root_dir, service_url):
1255 self._all_packages = set()
1256 self._mutator_lock = threading.Lock()
1257 self._packages_by_subdir = collections.defaultdict(list)
1258 self._root_dir = root_dir
1259 self._service_url = service_url
1260
1261 def add_package(self, subdir, package, version):
1262 """Adds a package to this CIPD root.
1263
1264 As far as clients are concerned, this grants both root and subdir authority
1265 to packages arbitrarily. (The implementation grants root authority to the
1266 first package added and subdir authority to the first package added for that
1267 subdir, but clients should not depend on or expect that behavior.)
1268
1269 Args:
1270 subdir: str; relative path to where the package should be installed from
1271 the cipd root directory.
1272 package: str; the cipd package name.
1273 version: str; the cipd package version.
1274 Returns:
1275 CipdPackage; the package that was created and added to this root.
1276 """
1277 with self._mutator_lock:
1278 cipd_package = CipdPackage(
1279 package, version,
1280 not self._packages_by_subdir,
1281 not self._packages_by_subdir[subdir])
1282 self._all_packages.add(cipd_package)
1283 self._packages_by_subdir[subdir].append(cipd_package)
1284 return cipd_package
1285
1286 def packages(self, subdir):
1287 """Get the list of configured packages for the given subdir."""
1288 return list(self._packages_by_subdir[subdir])
1289
1290 def clobber(self):
1291 """Remove the .cipd directory.
1292
1293 This is useful for forcing ensure to redownload and reinitialize all
1294 packages.
1295 """
1296 with self._mutator_lock:
1297 cipd_cache_dir = os.path.join(self._cipd_root, '.cipd')
1298 try:
1299 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1300 except OSError:
1301 if os.path.exists(cipd_cache_dir):
1302 raise
1303
1304 @contextlib.contextmanager
1305 def _create_ensure_file(self):
1306 try:
1307 ensure_file = None
1308 with tempfile.NamedTemporaryFile(
1309 suffix='.ensure', delete=False) as ensure_file:
1310 for subdir, packages in sorted(self._packages_by_subdir.iteritems()):
1311 ensure_file.write('@Subdir %s\n' % subdir)
1312 for package in packages:
1313 ensure_file.write('%s %s\n' % (package.name, package.version))
1314 ensure_file.write('\n')
1315 yield ensure_file.name
1316 finally:
1317 if ensure_file is not None and os.path.exists(ensure_file.name):
1318 os.remove(ensure_file.name)
1319
1320 def ensure(self):
1321 """Run `cipd ensure`."""
1322 with self._mutator_lock:
1323 with self._create_ensure_file() as ensure_file:
1324 cmd = [
1325 'cipd', 'ensure',
1326 '-log-level', 'error',
1327 '-root', self.root_dir,
1328 '-ensure-file', ensure_file,
1329 ]
1330 gclient_utils.CheckCallAndFilterAndHeader(cmd)
1331
1332 def created_package(self, package):
1333 """Checks whether this root created the given package.
1334
1335 Args:
1336 package: CipdPackage; the package to check.
1337 Returns:
1338 bool; whether this root created the given package.
1339 """
1340 return package in self._all_packages
1341
1342 @property
1343 def root_dir(self):
1344 return self._root_dir
1345
1346 @property
1347 def service_url(self):
1348 return self._service_url
1349
1350
1351class CipdWrapper(SCMWrapper):
1352 """Wrapper for CIPD.
1353
1354 Currently only supports chrome-infra-packages.appspot.com.
1355 """
1356
1357 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1358 out_cb=None, root=None, package=None):
1359 super(CipdWrapper, self).__init__(
1360 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1361 out_cb=out_cb)
1362 assert root.created_package(package)
1363 self._package = package
1364 self._root = root
1365
1366 #override
1367 def GetCacheMirror(self):
1368 return None
1369
1370 #override
1371 def GetActualRemoteURL(self, options):
1372 return self._root.service_url
1373
1374 #override
1375 def DoesRemoteURLMatch(self, options):
1376 del options
1377 return True
1378
1379 def revert(self, options, args, file_list):
1380 """Deletes .cipd and reruns ensure."""
1381 if self._package.authority_for_root:
1382 self._root.clobber()
1383 self._root.ensure()
1384
1385 def diff(self, options, args, file_list):
1386 """CIPD has no notion of diffing."""
1387 pass
1388
1389 def pack(self, options, args, file_list):
1390 """CIPD has no notion of diffing."""
1391 pass
1392
1393 def revinfo(self, options, args, file_list):
1394 """Grab the instance ID."""
1395 try:
1396 tmpdir = tempfile.mkdtemp()
1397 describe_json_path = os.path.join(tmpdir, 'describe.json')
1398 cmd = [
1399 'cipd', 'describe',
1400 self._package.name,
1401 '-log-level', 'error',
1402 '-version', self._package.version,
1403 '-json-output', describe_json_path
1404 ]
1405 gclient_utils.CheckCallAndFilter(
1406 cmd, filter_fn=lambda _line: None, print_stdout=False)
1407 with open(describe_json_path) as f:
1408 describe_json = json.load(f)
1409 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1410 finally:
1411 gclient_utils.rmtree(tmpdir)
1412
1413 def status(self, options, args, file_list):
1414 pass
1415
1416 def update(self, options, args, file_list):
1417 """Runs ensure."""
1418 if self._package.authority_for_root:
1419 self._root.ensure()