blob: 70ec6a95ce227e45df2dbdbd2a9e46b4013d88e9 [file] [log] [blame]
[email protected]93567042012-02-15 01:02:261# Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]5aeb7dd2009-11-17 18:09:012# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
[email protected]5f3eee32009-09-17 00:34:304
[email protected]d5800f12009-11-12 20:03:435"""Gclient-specific SCM-specific operations."""
[email protected]5f3eee32009-09-17 00:34:306
[email protected]fe0d1902014-04-08 20:50:447from __future__ import print_function
8
John Budorick0f7b2002018-01-19 23:46:179import collections
10import contextlib
[email protected]b2256212014-05-07 20:57:2811import errno
John Budorick0f7b2002018-01-19 23:46:1712import json
[email protected]754960e2009-09-21 12:31:0513import logging
[email protected]5f3eee32009-09-17 00:34:3014import os
[email protected]ee4071d2009-12-22 22:25:3715import posixpath
[email protected]5f3eee32009-09-17 00:34:3016import re
[email protected]90541732011-04-01 17:54:1817import sys
[email protected]3534aa52013-07-20 01:58:0818import tempfile
John Budorick0f7b2002018-01-19 23:46:1719import threading
[email protected]6279e8a2014-02-13 01:45:2520import traceback
[email protected]2f2ca142014-01-07 03:59:1821import urlparse
[email protected]5f3eee32009-09-17 00:34:3022
[email protected]2f2ca142014-01-07 03:59:1823import download_from_google_storage
[email protected]5f3eee32009-09-17 00:34:3024import gclient_utils
[email protected]848fd492014-04-09 19:06:4425import git_cache
[email protected]31cb48a2011-04-04 18:01:3626import scm
[email protected]b2256212014-05-07 20:57:2827import shutil
[email protected]31cb48a2011-04-04 18:01:3628import subprocess2
[email protected]5f3eee32009-09-17 00:34:3029
30
[email protected]71cbb502013-04-19 23:30:1531THIS_FILE_PATH = os.path.abspath(__file__)
32
[email protected]2f2ca142014-01-07 03:59:1833GSUTIL_DEFAULT_PATH = os.path.join(
[email protected]b091aa52014-12-20 01:47:3134 os.path.dirname(os.path.abspath(__file__)), 'gsutil.py')
[email protected]2f2ca142014-01-07 03:59:1835
[email protected]79d62372015-06-01 18:50:5536
smutae7ea312016-07-18 18:59:4137class NoUsableRevError(gclient_utils.Error):
38 """Raised if requested revision isn't found in checkout."""
39
40
[email protected]306080c2012-05-04 13:11:2941class DiffFiltererWrapper(object):
42 """Simple base class which tracks which file is being diffed and
[email protected]ee4071d2009-12-22 22:25:3743 replaces instances of its file name in the original and
agable41e3a6c2016-10-20 18:36:5644 working copy lines of the git diff output."""
[email protected]306080c2012-05-04 13:11:2945 index_string = None
[email protected]ee4071d2009-12-22 22:25:3746 original_prefix = "--- "
47 working_prefix = "+++ "
48
[email protected]fe0d1902014-04-08 20:50:4449 def __init__(self, relpath, print_func):
[email protected]ee4071d2009-12-22 22:25:3750 # Note that we always use '/' as the path separator to be
agable41e3a6c2016-10-20 18:36:5651 # consistent with cygwin-style output on Windows
[email protected]ee4071d2009-12-22 22:25:3752 self._relpath = relpath.replace("\\", "/")
[email protected]306080c2012-05-04 13:11:2953 self._current_file = None
[email protected]fe0d1902014-04-08 20:50:4454 self._print_func = print_func
[email protected]ee4071d2009-12-22 22:25:3755
[email protected]6e29d572010-06-04 17:32:2056 def SetCurrentFile(self, current_file):
57 self._current_file = current_file
[email protected]306080c2012-05-04 13:11:2958
[email protected]3830a672013-02-19 20:15:1459 @property
60 def _replacement_file(self):
[email protected]306080c2012-05-04 13:11:2961 return posixpath.join(self._relpath, self._current_file)
[email protected]ee4071d2009-12-22 22:25:3762
[email protected]f5d37bf2010-09-02 00:50:3463 def _Replace(self, line):
64 return line.replace(self._current_file, self._replacement_file)
[email protected]ee4071d2009-12-22 22:25:3765
66 def Filter(self, line):
67 if (line.startswith(self.index_string)):
68 self.SetCurrentFile(line[len(self.index_string):])
[email protected]f5d37bf2010-09-02 00:50:3469 line = self._Replace(line)
[email protected]ee4071d2009-12-22 22:25:3770 else:
71 if (line.startswith(self.original_prefix) or
72 line.startswith(self.working_prefix)):
[email protected]f5d37bf2010-09-02 00:50:3473 line = self._Replace(line)
[email protected]fe0d1902014-04-08 20:50:4474 self._print_func(line)
[email protected]ee4071d2009-12-22 22:25:3775
76
[email protected]306080c2012-05-04 13:11:2977class 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
[email protected]cb5442b2009-09-22 16:51:2488# SCMWrapper base class
89
[email protected]5f3eee32009-09-17 00:34:3090class SCMWrapper(object):
91 """Add necessary glue between all the supported SCM.
92
[email protected]d6504212010-01-13 17:34:3193 This is the abstraction layer to bind to different SCM.
94 """
[email protected]fe0d1902014-04-08 20:50:4495 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
Edward Lemur231f5ea2018-01-31 18:02:3696 out_cb=None, print_outbuf=False):
[email protected]5f3eee32009-09-17 00:34:3097 self.url = url
[email protected]5e73b0c2009-09-18 19:47:4898 self._root_dir = root_dir
99 if self._root_dir:
100 self._root_dir = self._root_dir.replace('/', os.sep)
101 self.relpath = relpath
102 if self.relpath:
103 self.relpath = self.relpath.replace('/', os.sep)
[email protected]e28e4982009-09-25 20:51:45104 if self.relpath and self._root_dir:
105 self.checkout_path = os.path.join(self._root_dir, self.relpath)
[email protected]fe0d1902014-04-08 20:50:44106 if out_fh is None:
107 out_fh = sys.stdout
108 self.out_fh = out_fh
109 self.out_cb = out_cb
Edward Lemur231f5ea2018-01-31 18:02:36110 self.print_outbuf = print_outbuf
[email protected]fe0d1902014-04-08 20:50:44111
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)
[email protected]5f3eee32009-09-17 00:34:30117
[email protected]5f3eee32009-09-17 00:34:30118 def RunCommand(self, command, options, args, file_list=None):
agabledebf6c82016-12-21 20:50:12119 commands = ['update', 'updatesingle', 'revert',
[email protected]4b5b1772010-04-08 01:52:56120 'revinfo', 'status', 'diff', 'pack', 'runhooks']
[email protected]5f3eee32009-09-17 00:34:30121
122 if not command in commands:
123 raise gclient_utils.Error('Unknown command %s' % command)
124
[email protected]cb5442b2009-09-22 16:51:24125 if not command in dir(self):
[email protected]ee4071d2009-12-22 22:25:37126 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
[email protected]9eda4112010-06-11 18:56:10127 command, self.__class__.__name__))
[email protected]cb5442b2009-09-22 16:51:24128
129 return getattr(self, command)(options, args, file_list)
130
[email protected]fa2b9b42014-08-22 18:08:53131 @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
[email protected]27a6f9a2016-05-28 00:21:49139 def GetCacheMirror(self):
Robert Iannuccia19649b2018-06-29 16:31:45140 if getattr(self, 'cache_dir', None):
[email protected]27a6f9a2016-05-28 00:21:49141 url, _ = gclient_utils.SplitUrlRevision(self.url)
142 return git_cache.Mirror(url)
143 return None
144
[email protected]d33eab32014-07-07 19:35:18145 def GetActualRemoteURL(self, options):
[email protected]88d10082014-03-21 17:24:48146 """Attempt to determine the remote URL for this SCMWrapper."""
[email protected]d33eab32014-07-07 19:35:18147 # Git
[email protected]bda475e2014-03-24 19:04:45148 if os.path.exists(os.path.join(self.checkout_path, '.git')):
[email protected]fa2b9b42014-08-22 18:08:53149 actual_remote_url = self._get_first_remote_url(self.checkout_path)
[email protected]4e9be262014-04-08 19:40:30150
[email protected]27a6f9a2016-05-28 00:21:49151 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)
[email protected]4e9be262014-04-08 19:40:30157 return actual_remote_url
[email protected]88d10082014-03-21 17:24:48158 return None
159
[email protected]4e9be262014-04-08 19:40:30160 def DoesRemoteURLMatch(self, options):
[email protected]88d10082014-03-21 17:24:48161 """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
[email protected]d33eab32014-07-07 19:35:18166 actual_remote_url = self.GetActualRemoteURL(options)
[email protected]88d10082014-03-21 17:24:48167 if actual_remote_url:
[email protected]8156c9f2014-04-01 16:41:36168 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
169 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
[email protected]88d10082014-03-21 17:24:48170 else:
171 # This may occur if the self.checkout_path exists but does not contain a
agable41e3a6c2016-10-20 18:36:56172 # valid git checkout.
[email protected]88d10082014-03-21 17:24:48173 return False
174
[email protected]b09097a2014-04-09 19:09:08175 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 """
[email protected]b2256212014-05-07 20:57:28181 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)
[email protected]b09097a2014-04-09 19:09:08205
[email protected]cb5442b2009-09-22 16:51:24206
[email protected]55e724e2010-03-11 19:36:49207class GitWrapper(SCMWrapper):
[email protected]e28e4982009-09-25 20:51:45208 """Wrapper for Git"""
[email protected]2702bcd2013-09-24 19:10:07209 name = 'git'
[email protected]1a60dca2013-11-26 14:06:26210 remote = 'origin'
[email protected]e28e4982009-09-25 20:51:45211
Robert Iannuccia19649b2018-06-29 16:31:45212 @property
213 def cache_dir(self):
214 try:
215 return git_cache.Mirror.GetCachePath()
216 except RuntimeError:
217 return None
[email protected]53456aa2013-07-03 19:38:34218
John Budorick0f7b2002018-01-19 23:46:17219 def __init__(self, url=None, *args, **kwargs):
[email protected]4e075672011-11-21 16:35:08220 """Removes 'git+' fake prefix from git URL."""
221 if url.startswith('git+http://') or url.startswith('git+https://'):
222 url = url[4:]
John Budorick0f7b2002018-01-19 23:46:17223 SCMWrapper.__init__(self, url, *args, **kwargs)
[email protected]848fd492014-04-09 19:06:44224 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
225 if self.out_cb:
226 filter_kwargs['predicate'] = self.out_cb
227 self.filter = gclient_utils.GitFilter(**filter_kwargs)
[email protected]4e075672011-11-21 16:35:08228
[email protected]9e3e82c2012-04-18 12:55:43229 @staticmethod
230 def BinaryExists():
231 """Returns true if the command exists."""
232 try:
233 # We assume git is newer than 1.7. See: crbug.com/114483
234 result, version = scm.GIT.AssertVersion('1.7')
235 if not result:
236 raise gclient_utils.Error('Git version is older than 1.7: %s' % version)
237 return result
238 except OSError:
239 return False
240
[email protected]885a9602013-05-31 09:54:40241 def GetCheckoutRoot(self):
242 return scm.GIT.GetCheckoutRoot(self.checkout_path)
243
[email protected]396e1a62013-07-03 19:41:04244 def GetRevisionDate(self, _revision):
[email protected]eaab7842011-04-28 09:07:58245 """Returns the given revision's date in ISO-8601 format (which contains the
246 time zone)."""
247 # TODO(floitsch): get the time-stamp of the given revision and not just the
248 # time-stamp of the currently checked out revision.
249 return self._Capture(['log', '-n', '1', '--format=%ai'])
250
Aaron Gablef4068aa2017-12-12 23:14:09251 def _GetDiffFilenames(self, base):
252 """Returns the names of files modified since base."""
253 return self._Capture(
254 # Filter to remove base if it is None.
255 filter(bool, ['-c', 'core.quotePath=false', 'diff', '--name-only', base])
256 ).split()
257
[email protected]396e1a62013-07-03 19:41:04258 def diff(self, options, _args, _file_list):
Aaron Gable1853f662018-02-12 23:45:56259 _, revision = gclient_utils.SplitUrlRevision(self.url)
260 if not revision:
261 revision = 'refs/remotes/%s/master' % self.remote
262 self._Run(['-c', 'core.quotePath=false', 'diff', revision], options)
[email protected]e28e4982009-09-25 20:51:45263
[email protected]396e1a62013-07-03 19:41:04264 def pack(self, _options, _args, _file_list):
[email protected]ee4071d2009-12-22 22:25:37265 """Generates a patch file which can be applied to the root of the
[email protected]d6504212010-01-13 17:34:31266 repository.
267
268 The patch file is generated from a diff of the merge base of HEAD and
269 its upstream branch.
270 """
raphael.kubo.da.costa05c83592016-08-04 15:32:41271 try:
272 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
273 except subprocess2.CalledProcessError:
274 merge_base = []
[email protected]17d01792010-09-01 18:07:10275 gclient_utils.CheckCallAndFilter(
raphael.kubo.da.costa05c83592016-08-04 15:32:41276 ['git', 'diff'] + merge_base,
[email protected]8469bf92010-09-03 19:03:15277 cwd=self.checkout_path,
[email protected]255f2be2014-12-05 22:19:55278 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
[email protected]ee4071d2009-12-22 22:25:37279
Robert Iannuccic41d8b92017-02-17 01:07:37280 def _Scrub(self, target, options):
281 """Scrubs out all changes in the local repo, back to the state of target."""
[email protected]50fd47f2014-02-13 01:03:19282 quiet = []
283 if not options.verbose:
284 quiet = ['--quiet']
Robert Iannuccic41d8b92017-02-17 01:07:37285 self._Run(['reset', '--hard', target] + quiet, options)
286 if options.force and options.delete_unversioned_trees:
287 # where `target` is a commit that contains both upper and lower case
288 # versions of the same file on a case insensitive filesystem, we are
289 # actually in a broken state here. The index will have both 'a' and 'A',
290 # but only one of them will exist on the disk. To progress, we delete
291 # everything that status thinks is modified.
Aaron Gable7817f022017-12-12 17:43:17292 output = self._Capture([
293 '-c', 'core.quotePath=false', 'status', '--porcelain'], strip=False)
Robert Iannuccia7a9ceb2017-02-17 01:38:06294 for line in output.splitlines():
Robert Iannuccic41d8b92017-02-17 01:07:37295 # --porcelain (v1) looks like:
296 # XY filename
297 try:
298 filename = line[3:]
299 self.Print('_____ Deleting residual after reset: %r.' % filename)
300 gclient_utils.rm_file_or_tree(
Robert Iannuccia7a9ceb2017-02-17 01:38:06301 os.path.join(self.checkout_path, filename))
Robert Iannuccic41d8b92017-02-17 01:07:37302 except OSError:
303 pass
304
John Budorick882c91e2018-07-12 22:11:41305 def _FetchAndReset(self, revision, file_list, options):
Robert Iannuccic41d8b92017-02-17 01:07:37306 """Equivalent to git fetch; git reset."""
Edward Lemur579c9862018-07-13 23:17:51307 self._SetFetchConfig(options)
[email protected]50fd47f2014-02-13 01:03:19308
[email protected]680f2172014-06-25 00:39:32309 self._Fetch(options, prune=True, quiet=options.verbose)
John Budorick882c91e2018-07-12 22:11:41310 self._Scrub(revision, options)
[email protected]50fd47f2014-02-13 01:03:19311 if file_list is not None:
Aaron Gable7817f022017-12-12 17:43:17312 files = self._Capture(
313 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
[email protected]50fd47f2014-02-13 01:03:19314 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
315
[email protected]8a139702014-06-20 15:55:01316 def _DisableHooks(self):
317 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
318 if not os.path.isdir(hook_dir):
319 return
320 for f in os.listdir(hook_dir):
321 if not f.endswith('.sample') and not f.endswith('.disabled'):
[email protected]41265562015-04-08 09:14:46322 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
323 if os.path.exists(disabled_hook_path):
324 os.remove(disabled_hook_path)
325 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
[email protected]8a139702014-06-20 15:55:01326
[email protected]30a07982016-04-07 21:35:19327 def _maybe_break_locks(self, options):
328 """This removes all .lock files from this repo's .git directory, if the
329 user passed the --break_repo_locks command line flag.
330
331 In particular, this will cleanup index.lock files, as well as ref lock
332 files.
333 """
334 if options.break_repo_locks:
335 git_dir = os.path.join(self.checkout_path, '.git')
336 for path, _, filenames in os.walk(git_dir):
337 for filename in filenames:
338 if filename.endswith('.lock'):
339 to_break = os.path.join(path, filename)
340 self.Print('breaking lock: %s' % (to_break,))
341 try:
342 os.remove(to_break)
343 except OSError as ex:
344 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
345 raise
346
Edward Lemur6a4e31b2018-08-10 19:59:02347 # TODO(ehmaldonado): Remove after bot_update is modified to pass the patch's
348 # branch.
349 def _GetTargetBranchForCommit(self, commit):
Edward Lemurca7d8812018-07-24 17:42:45350 """Get the remote branch a commit is part of."""
Edward Lemur6a4e31b2018-08-10 19:59:02351 _WELL_KNOWN_BRANCHES = [
352 'refs/remotes/origin/master',
353 'refs/remotes/origin/infra/config',
354 'refs/remotes/origin/lkgr',
355 ]
356 for branch in _WELL_KNOWN_BRANCHES:
357 if scm.GIT.IsAncestor(self.checkout_path, commit, branch):
358 return branch
Edward Lemurca7d8812018-07-24 17:42:45359 remote_refs = self._Capture(
Edward Lemur6a4e31b2018-08-10 19:59:02360 ['for-each-ref', 'refs/remotes/%s' % self.remote,
Edward Lemurca7d8812018-07-24 17:42:45361 '--format=%(refname)']).splitlines()
Edward Lemur6a4e31b2018-08-10 19:59:02362 for ref in sorted(remote_refs, reverse=True):
Edward Lemurca7d8812018-07-24 17:42:45363 if scm.GIT.IsAncestor(self.checkout_path, commit, ref):
364 return ref
365 self.Print('Failed to find a remote ref that contains %s. '
366 'Candidate refs were %s.' % (commit, remote_refs))
367 # Fallback to the commit we got.
368 # This means that apply_path_ref will try to find the merge-base between the
369 # patch and the commit (which is most likely the commit) and cherry-pick
370 # everything in between.
371 return commit
372
Edward Lemur6a4e31b2018-08-10 19:59:02373 def apply_patch_ref(self, patch_repo, patch_ref, target_branch, options,
374 file_list):
375 """Apply a patch on top of the revision we're synced at.
376
377 The patch ref is given by |patch_repo|@|patch_ref|, and the current revision
378 is |base_rev|.
379 We also need the |target_branch| that the patch was uploaded against. We use
380 it to find a merge base between |patch_rev| and |base_rev|, so we can find
381 what commits constitute the patch:
382
383 Graphically, it looks like this:
384
385 ... -> merge_base -> [possibly already landed commits] -> target_branch
386 \
387 -> [possibly not yet landed dependent CLs] -> patch_rev
388
389 Next, we apply the commits |merge_base..patch_rev| on top of whatever is
390 currently checked out, denoted |base_rev|. Typically, it'd be a revision
391 from |target_branch|, but this is not required.
392
393 Graphically, we cherry pick |merge_base..patch_rev| on top of |base_rev|:
394
395 ... -> base_rev -> [possibly not yet landed dependent CLs] -> patch_rev
396
397 After application, if |options.reset_patch_ref| is specified, we soft reset
398 the just cherry-picked changes, keeping them in git index only.
399
400 Args:
401 patch_repo: The patch origin. e.g. 'https://foo.googlesource.com/bar'
402 patch_ref: The ref to the patch. e.g. 'refs/changes/1234/34/1'.
403 target_branch: The branch the patch was uploaded against.
404 e.g. 'refs/heads/master' or 'refs/heads/infra/config'.
405 options: The options passed to gclient.
406 file_list: A list where modified files will be appended.
407 """
408
Edward Lemurca7d8812018-07-24 17:42:45409 # Abort any cherry-picks in progress.
410 try:
411 self._Capture(['cherry-pick', '--abort'])
412 except subprocess2.CalledProcessError:
413 pass
414
Edward Lesmesc621b212018-03-22 00:26:56415 base_rev = self._Capture(['rev-parse', 'HEAD'])
Edward Lemur6a4e31b2018-08-10 19:59:02416 target_branch = target_branch or self._GetTargetBranchForCommit(base_rev)
Edward Lesmesc621b212018-03-22 00:26:56417 self.Print('===Applying patch ref===')
Edward Lemur6a4e31b2018-08-10 19:59:02418 self.Print('Patch ref is %r @ %r. Target branch for patch is %r. '
419 'Current HEAD is %r. Current dir is %r' % (
420 patch_repo, patch_ref, target_branch, base_rev,
421 self.checkout_path))
Edward Lesmesc621b212018-03-22 00:26:56422 self._Capture(['reset', '--hard'])
423 self._Capture(['fetch', patch_repo, patch_ref])
Edward Lemurca7d8812018-07-24 17:42:45424 patch_rev = self._Capture(['rev-parse', 'FETCH_HEAD'])
Edward Lesmesc621b212018-03-22 00:26:56425
Edward Lemurca7d8812018-07-24 17:42:45426 try:
427 if not options.rebase_patch_ref:
428 self._Capture(['checkout', patch_rev])
429 else:
430 # Find the merge-base between the branch_rev and patch_rev to find out
431 # the changes we need to cherry-pick on top of base_rev.
Edward Lemur6a4e31b2018-08-10 19:59:02432 merge_base = self._Capture(['merge-base', target_branch, patch_rev])
Edward Lemurca7d8812018-07-24 17:42:45433 self.Print('Merge base of %s and %s is %s' % (
Edward Lemur6a4e31b2018-08-10 19:59:02434 target_branch, patch_rev, merge_base))
Edward Lemurca7d8812018-07-24 17:42:45435 if merge_base == patch_rev:
436 # If the merge-base is patch_rev, it means patch_rev is already part
437 # of the history, so just check it out.
438 self._Capture(['checkout', patch_rev])
439 else:
440 # If a change was uploaded on top of another change, which has already
441 # landed, one of the commits in the cherry-pick range will be
442 # redundant, since it has already landed and its changes incorporated
443 # in the tree.
444 # We pass '--keep-redundant-commits' to ignore those changes.
445 self._Capture(['cherry-pick', merge_base + '..' + patch_rev,
446 '--keep-redundant-commits'])
447
448 if file_list is not None:
449 file_list.extend(self._GetDiffFilenames(base_rev))
450
451 except subprocess2.CalledProcessError as e:
Edward Lemur6a4e31b2018-08-10 19:59:02452 self.Print('Failed to apply patch.')
453 self.Print('Patch ref is %r @ %r. Target branch for patch is %r. '
454 'Current HEAD is %r. Current dir is %r' % (
455 patch_repo, patch_ref, target_branch, base_rev,
456 self.checkout_path))
Edward Lemurca7d8812018-07-24 17:42:45457 self.Print('git returned non-zero exit status %s:\n%s' % (
458 e.returncode, e.stderr))
459 # Print the current status so that developers know what changes caused the
460 # patch failure, since git cherry-pick doesn't show that information.
461 self.Print(self._Capture(['status']))
John Budorick2c984a02018-07-18 23:24:13462 try:
Edward Lemurca7d8812018-07-24 17:42:45463 self._Capture(['cherry-pick', '--abort'])
464 except subprocess2.CalledProcessError:
465 pass
466 raise
467
Edward Lesmesc621b212018-03-22 00:26:56468 if options.reset_patch_ref:
469 self._Capture(['reset', '--soft', base_rev])
470
[email protected]e28e4982009-09-25 20:51:45471 def update(self, options, args, file_list):
472 """Runs git to update or transparently checkout the working copy.
473
474 All updated files will be appended to file_list.
475
476 Raises:
477 Error: if can't get URL for relative path.
478 """
[email protected]e28e4982009-09-25 20:51:45479 if args:
480 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
481
[email protected]ece406f2010-02-23 17:29:15482 self._CheckMinVersion("1.6.6")
[email protected]923a0372009-12-11 20:42:43483
John Budorick882c91e2018-07-12 22:11:41484 # If a dependency is not pinned, track the default remote branch.
485 default_rev = 'refs/remotes/%s/master' % self.remote
[email protected]7080e942010-03-15 15:06:16486 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
[email protected]7080e942010-03-15 15:06:16487 revision = deps_revision
[email protected]eb2756d2011-09-20 20:17:51488 managed = True
[email protected]e28e4982009-09-25 20:51:45489 if options.revision:
[email protected]ac915bb2009-11-13 17:03:01490 # Override the revision number.
491 revision = str(options.revision)
[email protected]eb2756d2011-09-20 20:17:51492 if revision == 'unmanaged':
[email protected]483a0ba2014-05-30 00:06:07493 # Check again for a revision in case an initial ref was specified
494 # in the url, for example bla.git@refs/heads/custombranch
495 revision = deps_revision
[email protected]eb2756d2011-09-20 20:17:51496 managed = False
[email protected]d90ba3f2010-02-23 14:42:57497 if not revision:
498 revision = default_rev
[email protected]e28e4982009-09-25 20:51:45499
[email protected]8a139702014-06-20 15:55:01500 if managed:
501 self._DisableHooks()
502
[email protected]d90ba3f2010-02-23 14:42:57503 printed_path = False
504 verbose = []
[email protected]b1a22bf2009-11-07 02:33:50505 if options.verbose:
agable83faed02016-10-24 21:37:10506 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
[email protected]d90ba3f2010-02-23 14:42:57507 verbose = ['--verbose']
508 printed_path = True
509
John Budorick882c91e2018-07-12 22:11:41510 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
511 if remote_ref:
512 # Rewrite remote refs to their local equivalents.
513 revision = ''.join(remote_ref)
514 rev_type = "branch"
515 elif revision.startswith('refs/'):
516 # Local branch? We probably don't want to support, since DEPS should
517 # always specify branches as they are in the upstream repo.
518 rev_type = "branch"
519 else:
520 # hash is also a tag, only make a distinction at checkout
521 rev_type = "hash"
[email protected]d33eab32014-07-07 19:35:18522
John Budorick882c91e2018-07-12 22:11:41523 mirror = self._GetMirror(url, options)
[email protected]b0a13a22014-06-18 00:52:25524 if mirror:
525 url = mirror.mirror_path
526
[email protected]1c127382015-02-17 11:15:40527 # If we are going to introduce a new project, there is a possibility that
528 # we are syncing back to a state where the project was originally a
529 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point
530 # syncing backwards, when Blink was a DEPS entry and not part of src.git).
531 # In such case, we might have a backup of the former .git folder, which can
532 # be used to avoid re-fetching the entire repo again (useful for bisects).
533 backup_dir = self.GetGitBackupDirPath()
534 target_dir = os.path.join(self.checkout_path, '.git')
535 if os.path.exists(backup_dir) and not os.path.exists(target_dir):
536 gclient_utils.safe_makedirs(self.checkout_path)
537 os.rename(backup_dir, target_dir)
538 # Reset to a clean state
Robert Iannuccic41d8b92017-02-17 01:07:37539 self._Scrub('HEAD', options)
[email protected]1c127382015-02-17 11:15:40540
[email protected]6c2b49d2014-02-26 23:57:38541 if (not os.path.exists(self.checkout_path) or
542 (os.path.isdir(self.checkout_path) and
543 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
[email protected]b0a13a22014-06-18 00:52:25544 if mirror:
John Budorick882c91e2018-07-12 22:11:41545 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
[email protected]90fe58b2014-05-01 18:22:00546 try:
John Budorick882c91e2018-07-12 22:11:41547 self._Clone(revision, url, options)
[email protected]90fe58b2014-05-01 18:22:00548 except subprocess2.CalledProcessError:
549 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:41550 self._Clone(revision, url, options)
[email protected]396e1a62013-07-03 19:41:04551 if file_list is not None:
Aaron Gable7817f022017-12-12 17:43:17552 files = self._Capture(
John Budorick21a51b32018-09-19 19:39:20553 ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
[email protected]396e1a62013-07-03 19:41:04554 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
John Budorick21a51b32018-09-19 19:39:20555 if mirror:
556 self._Capture(
557 ['remote', 'set-url', '--push', 'origin', mirror.url])
[email protected]d90ba3f2010-02-23 14:42:57558 if not verbose:
559 # Make the output a little prettier. It's nice to have some whitespace
560 # between projects when cloning.
[email protected]fe0d1902014-04-08 20:50:44561 self.Print('')
[email protected]2702bcd2013-09-24 19:10:07562 return self._Capture(['rev-parse', '--verify', 'HEAD'])
[email protected]e28e4982009-09-25 20:51:45563
John Budorick21a51b32018-09-19 19:39:20564 if mirror:
565 self._Capture(
566 ['remote', 'set-url', '--push', 'origin', mirror.url])
567
[email protected]3dc5cb72014-06-17 15:06:05568 if not managed:
Edward Lemur579c9862018-07-13 23:17:51569 self._SetFetchConfig(options)
[email protected]3dc5cb72014-06-17 15:06:05570 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
571 return self._Capture(['rev-parse', '--verify', 'HEAD'])
572
[email protected]30a07982016-04-07 21:35:19573 self._maybe_break_locks(options)
574
[email protected]b0a13a22014-06-18 00:52:25575 if mirror:
John Budorick882c91e2018-07-12 22:11:41576 self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
[email protected]b0a13a22014-06-18 00:52:25577
[email protected]6c2b49d2014-02-26 23:57:38578 # See if the url has changed (the unittests use git://foo for the url, let
579 # that through).
580 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
581 return_early = False
582 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
583 # unit test pass. (and update the comment above)
584 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
585 # This allows devs to use experimental repos which have a different url
586 # but whose branch(s) are the same as official repos.
[email protected]b09097a2014-04-09 19:09:08587 if (current_url.rstrip('/') != url.rstrip('/') and
[email protected]6c2b49d2014-02-26 23:57:38588 url != 'git://foo' and
589 subprocess2.capture(
590 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
591 cwd=self.checkout_path).strip() != 'False'):
[email protected]fe0d1902014-04-08 20:50:44592 self.Print('_____ switching %s to a new upstream' % self.relpath)
[email protected]78514212014-08-20 23:08:00593 if not (options.force or options.reset):
594 # Make sure it's clean
agable83faed02016-10-24 21:37:10595 self._CheckClean(revision)
[email protected]6c2b49d2014-02-26 23:57:38596 # Switch over to the new upstream
597 self._Run(['remote', 'set-url', self.remote, url], options)
[email protected]8dd35462015-06-08 22:56:05598 if mirror:
599 with open(os.path.join(
600 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
601 'w') as fh:
602 fh.write(os.path.join(url, 'objects'))
John Budorick882c91e2018-07-12 22:11:41603 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
604 self._FetchAndReset(revision, file_list, options)
[email protected]c438c142015-08-24 22:55:55605
[email protected]6c2b49d2014-02-26 23:57:38606 return_early = True
[email protected]c438c142015-08-24 22:55:55607 else:
John Budorick882c91e2018-07-12 22:11:41608 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
[email protected]6c2b49d2014-02-26 23:57:38609
[email protected]50fd47f2014-02-13 01:03:19610 if return_early:
611 return self._Capture(['rev-parse', '--verify', 'HEAD'])
612
[email protected]5bde4852009-12-14 16:47:12613 cur_branch = self._GetCurrentBranch()
614
[email protected]d90ba3f2010-02-23 14:42:57615 # Cases:
[email protected]786fb682010-06-02 15:16:23616 # 0) HEAD is detached. Probably from our initial clone.
617 # - make sure HEAD is contained by a named ref, then update.
618 # Cases 1-4. HEAD is a branch.
agable41e3a6c2016-10-20 18:36:56619 # 1) current branch is not tracking a remote branch
[email protected]786fb682010-06-02 15:16:23620 # - try to rebase onto the new hash or branch
621 # 2) current branch is tracking a remote branch with local committed
622 # changes, but the DEPS file switched to point to a hash
[email protected]d90ba3f2010-02-23 14:42:57623 # - rebase those changes on top of the hash
[email protected]6e7202b2014-09-09 18:23:39624 # 3) current branch is tracking a remote branch w/or w/out changes, and
625 # no DEPS switch
[email protected]d90ba3f2010-02-23 14:42:57626 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
[email protected]6e7202b2014-09-09 18:23:39627 # 4) current branch is tracking a remote branch, but DEPS switches to a
628 # different remote branch, and
629 # a) current branch has no local changes, and --force:
630 # - checkout new branch
631 # b) current branch has local changes, and --force and --reset:
632 # - checkout new branch
633 # c) otherwise exit
[email protected]d90ba3f2010-02-23 14:42:57634
[email protected]81e012c2010-04-29 16:07:24635 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
636 # a tracking branch
[email protected]d90ba3f2010-02-23 14:42:57637 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
638 # or it returns None if it couldn't find an upstream
[email protected]786fb682010-06-02 15:16:23639 if cur_branch is None:
640 upstream_branch = None
641 current_type = "detached"
642 logging.debug("Detached HEAD")
[email protected]d90ba3f2010-02-23 14:42:57643 else:
[email protected]786fb682010-06-02 15:16:23644 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
645 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
646 current_type = "hash"
647 logging.debug("Current branch is not tracking an upstream (remote)"
648 " branch.")
649 elif upstream_branch.startswith('refs/remotes'):
650 current_type = "branch"
651 else:
652 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
[email protected]d90ba3f2010-02-23 14:42:57653
Edward Lemur579c9862018-07-13 23:17:51654 self._SetFetchConfig(options)
655 self._Fetch(options, prune=options.force)
656
John Budorick882c91e2018-07-12 22:11:41657 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
[email protected]cbd20a42012-06-27 13:49:27658 # Update the remotes first so we have all the refs.
[email protected]a41249c2013-07-03 00:09:12659 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
[email protected]cbd20a42012-06-27 13:49:27660 cwd=self.checkout_path)
[email protected]cbd20a42012-06-27 13:49:27661 if verbose:
[email protected]fe0d1902014-04-08 20:50:44662 self.Print(remote_output)
[email protected]d90ba3f2010-02-23 14:42:57663
John Budorick882c91e2018-07-12 22:11:41664 revision = self._AutoFetchRef(options, revision)
Paweł Hajdan, Jr63b8c2a2017-09-05 15:59:08665
[email protected]d90ba3f2010-02-23 14:42:57666 # This is a big hammer, debatable if it should even be here...
[email protected]793796d2010-02-19 17:27:41667 if options.force or options.reset:
[email protected]d4fffee2013-06-28 00:35:26668 target = 'HEAD'
669 if options.upstream and upstream_branch:
670 target = upstream_branch
Robert Iannuccic41d8b92017-02-17 01:07:37671 self._Scrub(target, options)
[email protected]d90ba3f2010-02-23 14:42:57672
[email protected]786fb682010-06-02 15:16:23673 if current_type == 'detached':
674 # case 0
Robert Iannuccic41d8b92017-02-17 01:07:37675 # We just did a Scrub, this is as clean as it's going to get. In
676 # particular if HEAD is a commit that contains two versions of the same
677 # file on a case-insensitive filesystem (e.g. 'a' and 'A'), there's no way
678 # to actually "Clean" the checkout; that commit is uncheckoutable on this
679 # system. The best we can do is carry forward to the checkout step.
680 if not (options.force or options.reset):
John Budorick882c91e2018-07-12 22:11:41681 self._CheckClean(revision)
682 self._CheckDetachedHead(revision, options)
[email protected]d33eab32014-07-07 19:35:18683 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
[email protected]848fd492014-04-09 19:06:44684 self.Print('Up-to-date; skipping checkout.')
685 else:
[email protected]2b7d3ed2014-06-20 18:15:37686 # 'git checkout' may need to overwrite existing untracked files. Allow
687 # it only when nuclear options are enabled.
[email protected]bb424c02014-06-23 22:42:51688 self._Checkout(
689 options,
John Budorick882c91e2018-07-12 22:11:41690 revision,
[email protected]34b4e982016-05-16 19:06:07691 force=(options.force and options.delete_unversioned_trees),
[email protected]bb424c02014-06-23 22:42:51692 quiet=True,
693 )
[email protected]786fb682010-06-02 15:16:23694 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41695 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
[email protected]786fb682010-06-02 15:16:23696 elif current_type == 'hash':
[email protected]d90ba3f2010-02-23 14:42:57697 # case 1
agable41e3a6c2016-10-20 18:36:56698 # Can't find a merge-base since we don't know our upstream. That makes
699 # this command VERY likely to produce a rebase failure. For now we
700 # assume origin is our upstream since that's what the old behavior was.
John Budorick882c91e2018-07-12 22:11:41701 upstream_branch = self.remote
702 if options.revision or deps_revision:
703 upstream_branch = revision
agable1a8439a2016-10-24 23:36:14704 self._AttemptRebase(upstream_branch, file_list, options,
agable41e3a6c2016-10-20 18:36:56705 printed_path=printed_path, merge=options.merge)
706 printed_path = True
John Budorick882c91e2018-07-12 22:11:41707 elif rev_type == 'hash':
708 # case 2
709 self._AttemptRebase(upstream_branch, file_list, options,
710 newbase=revision, printed_path=printed_path,
711 merge=options.merge)
712 printed_path = True
713 elif remote_ref and ''.join(remote_ref) != upstream_branch:
[email protected]d90ba3f2010-02-23 14:42:57714 # case 4
John Budorick882c91e2018-07-12 22:11:41715 new_base = ''.join(remote_ref)
[email protected]d90ba3f2010-02-23 14:42:57716 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41717 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
[email protected]6e7202b2014-09-09 18:23:39718 switch_error = ("Could not switch upstream branch from %s to %s\n"
John Budorick882c91e2018-07-12 22:11:41719 % (upstream_branch, new_base) +
[email protected]6e7202b2014-09-09 18:23:39720 "Please use --force or merge or rebase manually:\n" +
John Budorick882c91e2018-07-12 22:11:41721 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
722 "OR git checkout -b <some new branch> %s" % new_base)
[email protected]6e7202b2014-09-09 18:23:39723 force_switch = False
724 if options.force:
725 try:
John Budorick882c91e2018-07-12 22:11:41726 self._CheckClean(revision)
[email protected]6e7202b2014-09-09 18:23:39727 # case 4a
728 force_switch = True
729 except gclient_utils.Error as e:
730 if options.reset:
731 # case 4b
732 force_switch = True
733 else:
734 switch_error = '%s\n%s' % (e.message, switch_error)
735 if force_switch:
736 self.Print("Switching upstream branch from %s to %s" %
John Budorick882c91e2018-07-12 22:11:41737 (upstream_branch, new_base))
738 switch_branch = 'gclient_' + remote_ref[1]
739 self._Capture(['branch', '-f', switch_branch, new_base])
[email protected]6e7202b2014-09-09 18:23:39740 self._Checkout(options, switch_branch, force=True, quiet=True)
741 else:
742 # case 4c
743 raise gclient_utils.Error(switch_error)
[email protected]d90ba3f2010-02-23 14:42:57744 else:
745 # case 3 - the default case
Aaron Gablef4068aa2017-12-12 23:14:09746 rebase_files = self._GetDiffFilenames(upstream_branch)
[email protected]d90ba3f2010-02-23 14:42:57747 if verbose:
[email protected]fe0d1902014-04-08 20:50:44748 self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
[email protected]d90ba3f2010-02-23 14:42:57749 try:
[email protected]2aad1b22011-07-22 12:00:41750 merge_args = ['merge']
[email protected]30c46d62014-01-23 12:11:56751 if options.merge:
752 merge_args.append('--ff')
753 else:
[email protected]2aad1b22011-07-22 12:00:41754 merge_args.append('--ff-only')
755 merge_args.append(upstream_branch)
[email protected]fe0d1902014-04-08 20:50:44756 merge_output = self._Capture(merge_args)
[email protected]18fa4542013-05-21 13:30:46757 except subprocess2.CalledProcessError as e:
agable1a8439a2016-10-24 23:36:14758 rebase_files = []
[email protected]d90ba3f2010-02-23 14:42:57759 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
760 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41761 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 21:37:10762 timestamp=False)
[email protected]d90ba3f2010-02-23 14:42:57763 printed_path = True
764 while True:
[email protected]5b23e872015-02-20 21:25:57765 if not options.auto_rebase:
766 try:
767 action = self._AskForData(
768 'Cannot %s, attempt to rebase? '
769 '(y)es / (q)uit / (s)kip : ' %
770 ('merge' if options.merge else 'fast-forward merge'),
771 options)
772 except ValueError:
773 raise gclient_utils.Error('Invalid Character')
774 if options.auto_rebase or re.match(r'yes|y', action, re.I):
agable1a8439a2016-10-24 23:36:14775 self._AttemptRebase(upstream_branch, rebase_files, options,
[email protected]30c46d62014-01-23 12:11:56776 printed_path=printed_path, merge=False)
[email protected]d90ba3f2010-02-23 14:42:57777 printed_path = True
778 break
779 elif re.match(r'quit|q', action, re.I):
780 raise gclient_utils.Error("Can't fast-forward, please merge or "
781 "rebase manually.\n"
782 "cd %s && git " % self.checkout_path
783 + "rebase %s" % upstream_branch)
784 elif re.match(r'skip|s', action, re.I):
[email protected]fe0d1902014-04-08 20:50:44785 self.Print('Skipping %s' % self.relpath)
[email protected]d90ba3f2010-02-23 14:42:57786 return
787 else:
[email protected]fe0d1902014-04-08 20:50:44788 self.Print('Input not recognized')
[email protected]27c9c8a2014-09-11 19:57:55789 elif re.match("error: Your local changes to '.*' would be "
790 "overwritten by merge. Aborting.\nPlease, commit your "
791 "changes or stash them before you can merge.\n",
[email protected]d90ba3f2010-02-23 14:42:57792 e.stderr):
793 if not printed_path:
John Budorick882c91e2018-07-12 22:11:41794 self.Print('_____ %s at %s' % (self.relpath, revision),
agable83faed02016-10-24 21:37:10795 timestamp=False)
[email protected]d90ba3f2010-02-23 14:42:57796 printed_path = True
797 raise gclient_utils.Error(e.stderr)
798 else:
799 # Some other problem happened with the merge
800 logging.error("Error during fast-forward merge in %s!" % self.relpath)
[email protected]fe0d1902014-04-08 20:50:44801 self.Print(e.stderr)
[email protected]d90ba3f2010-02-23 14:42:57802 raise
803 else:
804 # Fast-forward merge was successful
805 if not re.match('Already up-to-date.', merge_output) or verbose:
806 if not printed_path:
agable83faed02016-10-24 21:37:10807 self.Print('_____ %s at %s' % (self.relpath, revision),
808 timestamp=False)
[email protected]d90ba3f2010-02-23 14:42:57809 printed_path = True
[email protected]fe0d1902014-04-08 20:50:44810 self.Print(merge_output.strip())
[email protected]d90ba3f2010-02-23 14:42:57811 if not verbose:
812 # Make the output a little prettier. It's nice to have some
813 # whitespace between projects when syncing.
[email protected]fe0d1902014-04-08 20:50:44814 self.Print('')
[email protected]d90ba3f2010-02-23 14:42:57815
agablec3937b92016-10-25 17:13:03816 if file_list is not None:
817 file_list.extend(
818 [os.path.join(self.checkout_path, f) for f in rebase_files])
[email protected]5bde4852009-12-14 16:47:12819
820 # If the rebase generated a conflict, abort and ask user to fix
[email protected]786fb682010-06-02 15:16:23821 if self._IsRebasing():
agable83faed02016-10-24 21:37:10822 raise gclient_utils.Error('\n____ %s at %s\n'
[email protected]5bde4852009-12-14 16:47:12823 '\nConflict while rebasing this branch.\n'
824 'Fix the conflict and run gclient again.\n'
825 'See man git-rebase for details.\n'
agable83faed02016-10-24 21:37:10826 % (self.relpath, revision))
[email protected]5bde4852009-12-14 16:47:12827
[email protected]d90ba3f2010-02-23 14:42:57828 if verbose:
[email protected]fe0d1902014-04-08 20:50:44829 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
830 timestamp=False)
[email protected]e28e4982009-09-25 20:51:45831
[email protected]98e69452012-02-16 16:36:43832 # If --reset and --delete_unversioned_trees are specified, remove any
833 # untracked directories.
834 if options.reset and options.delete_unversioned_trees:
835 # GIT.CaptureStatus() uses 'dit diff' to compare to a specific SHA1 (the
836 # merge-base by default), so doesn't include untracked files. So we use
837 # 'git ls-files --directory --others --exclude-standard' here directly.
838 paths = scm.GIT.Capture(
Aaron Gable7817f022017-12-12 17:43:17839 ['-c', 'core.quotePath=false', 'ls-files',
840 '--directory', '--others', '--exclude-standard'],
[email protected]98e69452012-02-16 16:36:43841 self.checkout_path)
842 for path in (p for p in paths.splitlines() if p.endswith('/')):
843 full_path = os.path.join(self.checkout_path, path)
844 if not os.path.islink(full_path):
[email protected]fe0d1902014-04-08 20:50:44845 self.Print('_____ removing unversioned directory %s' % path)
[email protected]dc112ac2013-04-24 13:00:19846 gclient_utils.rmtree(full_path)
[email protected]98e69452012-02-16 16:36:43847
[email protected]2702bcd2013-09-24 19:10:07848 return self._Capture(['rev-parse', '--verify', 'HEAD'])
849
[email protected]396e1a62013-07-03 19:41:04850 def revert(self, options, _args, file_list):
[email protected]e28e4982009-09-25 20:51:45851 """Reverts local modifications.
852
853 All reverted files will be appended to file_list.
854 """
[email protected]8469bf92010-09-03 19:03:15855 if not os.path.isdir(self.checkout_path):
[email protected]260c6532009-10-28 03:22:35856 # revert won't work if the directory doesn't exist. It needs to
857 # checkout instead.
[email protected]fe0d1902014-04-08 20:50:44858 self.Print('_____ %s is missing, synching instead' % self.relpath)
[email protected]260c6532009-10-28 03:22:35859 # Don't reuse the args.
860 return self.update(options, [], file_list)
[email protected]b2b46312010-04-30 20:58:03861
862 default_rev = "refs/heads/master"
[email protected]d4fffee2013-06-28 00:35:26863 if options.upstream:
864 if self._GetCurrentBranch():
865 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
866 default_rev = upstream_branch or default_rev
[email protected]6e29d572010-06-04 17:32:20867 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
[email protected]b2b46312010-04-30 20:58:03868 if not deps_revision:
869 deps_revision = default_rev
[email protected]d33eab32014-07-07 19:35:18870 if deps_revision.startswith('refs/heads/'):
871 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/')
smutae7ea312016-07-18 18:59:41872 try:
873 deps_revision = self.GetUsableRev(deps_revision, options)
874 except NoUsableRevError as e:
875 # If the DEPS entry's url and hash changed, try to update the origin.
876 # See also http://crbug.com/520067.
877 logging.warn(
878 'Couldn\'t find usable revision, will retrying to update instead: %s',
879 e.message)
880 return self.update(options, [], file_list)
[email protected]b2b46312010-04-30 20:58:03881
[email protected]396e1a62013-07-03 19:41:04882 if file_list is not None:
Aaron Gablef4068aa2017-12-12 23:14:09883 files = self._GetDiffFilenames(deps_revision)
[email protected]396e1a62013-07-03 19:41:04884
Robert Iannuccic41d8b92017-02-17 01:07:37885 self._Scrub(deps_revision, options)
[email protected]ade83db2012-09-27 14:06:49886 self._Run(['clean', '-f', '-d'], options)
[email protected]e28e4982009-09-25 20:51:45887
[email protected]396e1a62013-07-03 19:41:04888 if file_list is not None:
889 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
890
891 def revinfo(self, _options, _args, _file_list):
[email protected]6cafa132010-09-07 14:17:26892 """Returns revision"""
893 return self._Capture(['rev-parse', 'HEAD'])
[email protected]0f282062009-11-06 20:14:02894
[email protected]e28e4982009-09-25 20:51:45895 def runhooks(self, options, args, file_list):
896 self.status(options, args, file_list)
897
[email protected]396e1a62013-07-03 19:41:04898 def status(self, options, _args, file_list):
[email protected]e28e4982009-09-25 20:51:45899 """Display status information."""
900 if not os.path.isdir(self.checkout_path):
[email protected]fe0d1902014-04-08 20:50:44901 self.Print('________ couldn\'t run status in %s:\n'
902 'The directory does not exist.' % self.checkout_path)
[email protected]e28e4982009-09-25 20:51:45903 else:
raphael.kubo.da.costa05c83592016-08-04 15:32:41904 try:
905 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])]
906 except subprocess2.CalledProcessError:
907 merge_base = []
Aaron Gablef4068aa2017-12-12 23:14:09908 self._Run(
909 ['-c', 'core.quotePath=false', 'diff', '--name-status'] + merge_base,
910 options, stdout=self.out_fh, always=options.verbose)
[email protected]396e1a62013-07-03 19:41:04911 if file_list is not None:
Aaron Gablef4068aa2017-12-12 23:14:09912 files = self._GetDiffFilenames(merge_base[0] if merge_base else None)
[email protected]396e1a62013-07-03 19:41:04913 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
[email protected]e28e4982009-09-25 20:51:45914
smutae7ea312016-07-18 18:59:41915 def GetUsableRev(self, rev, options):
agable41e3a6c2016-10-20 18:36:56916 """Finds a useful revision for this repository."""
smutae7ea312016-07-18 18:59:41917 sha1 = None
918 if not os.path.isdir(self.checkout_path):
919 raise NoUsableRevError(
agablea98a6cd2016-11-15 22:30:10920 'This is not a git repo, so we cannot get a usable rev.')
agable41e3a6c2016-10-20 18:36:56921
922 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
923 sha1 = rev
smutae7ea312016-07-18 18:59:41924 else:
agable41e3a6c2016-10-20 18:36:56925 # May exist in origin, but we don't have it yet, so fetch and look
926 # again.
927 self._Fetch(options)
smutae7ea312016-07-18 18:59:41928 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev):
929 sha1 = rev
smutae7ea312016-07-18 18:59:41930
931 if not sha1:
932 raise NoUsableRevError(
agablea98a6cd2016-11-15 22:30:10933 'Hash %s does not appear to be a valid hash in this repo.' % rev)
smutae7ea312016-07-18 18:59:41934
935 return sha1
936
[email protected]1c127382015-02-17 11:15:40937 def GetGitBackupDirPath(self):
938 """Returns the path where the .git folder for the current project can be
939 staged/restored. Use case: subproject moved from DEPS <-> outer project."""
940 return os.path.join(self._root_dir,
941 'old_' + self.relpath.replace(os.sep, '_')) + '.git'
942
John Budorick882c91e2018-07-12 22:11:41943 def _GetMirror(self, url, options):
[email protected]b0a13a22014-06-18 00:52:25944 """Get a git_cache.Mirror object for the argument url."""
Robert Iannuccia19649b2018-06-29 16:31:45945 if not self.cache_dir:
[email protected]b0a13a22014-06-18 00:52:25946 return None
[email protected]b1b54572014-04-16 22:29:23947 mirror_kwargs = {
948 'print_func': self.filter,
John Budorick882c91e2018-07-12 22:11:41949 'refs': []
[email protected]b1b54572014-04-16 22:29:23950 }
[email protected]b1b54572014-04-16 22:29:23951 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
952 mirror_kwargs['refs'].append('refs/branch-heads/*')
[email protected]8d3348f2014-08-19 22:49:16953 if hasattr(options, 'with_tags') and options.with_tags:
954 mirror_kwargs['refs'].append('refs/tags/*')
[email protected]b0a13a22014-06-18 00:52:25955 return git_cache.Mirror(url, **mirror_kwargs)
956
John Budorick882c91e2018-07-12 22:11:41957 def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
Andrii Shyshkalov46a672b2017-11-25 02:04:43958 """Update a git mirror by fetching the latest commits from the remote,
959 unless mirror already contains revision whose type is sha1 hash.
960 """
John Budorick882c91e2018-07-12 22:11:41961 if rev_type == 'hash' and mirror.contains_revision(revision):
Andrii Shyshkalov46a672b2017-11-25 02:04:43962 if options.verbose:
963 self.Print('skipping mirror update, it has rev=%s already' % revision,
964 timestamp=False)
965 return
966
[email protected]3ec84f62014-08-22 21:00:22967 if getattr(options, 'shallow', False):
[email protected]46b87412014-05-15 00:42:05968 # HACK(hinoka): These repositories should be super shallow.
[email protected]b0a13a22014-06-18 00:52:25969 if 'flash' in mirror.url:
[email protected]46b87412014-05-15 00:42:05970 depth = 10
971 else:
972 depth = 10000
973 else:
974 depth = None
[email protected]e8bc1aa2015-04-08 08:00:37975 mirror.populate(verbose=options.verbose,
976 bootstrap=not getattr(options, 'no_bootstrap', False),
Vadim Shtayura08049e22017-10-11 00:14:52977 depth=depth,
978 ignore_lock=getattr(options, 'ignore_locks', False),
979 lock_timeout=getattr(options, 'lock_timeout', 0))
980 mirror.unlock()
[email protected]53456aa2013-07-03 19:38:34981
John Budorick882c91e2018-07-12 22:11:41982 def _Clone(self, revision, url, options):
[email protected]d90ba3f2010-02-23 14:42:57983 """Clone a git repository from the given URL.
984
[email protected]786fb682010-06-02 15:16:23985 Once we've cloned the repo, we checkout a working branch if the specified
986 revision is a branch head. If it is a tag or a specific commit, then we
987 leave HEAD detached as it makes future updates simpler -- in this case the
988 user should first create a new branch or switch to an existing branch before
989 making changes in the repo."""
[email protected]f5d37bf2010-09-02 00:50:34990 if not options.verbose:
[email protected]d90ba3f2010-02-23 14:42:57991 # git clone doesn't seem to insert a newline properly before printing
992 # to stdout
[email protected]fe0d1902014-04-08 20:50:44993 self.Print('')
[email protected]f1d73eb2014-04-21 17:07:04994 cfg = gclient_utils.DefaultIndexPackConfig(url)
[email protected]8a139702014-06-20 15:55:01995 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
[email protected]53456aa2013-07-03 19:38:34996 if self.cache_dir:
997 clone_cmd.append('--shared')
[email protected]f5d37bf2010-09-02 00:50:34998 if options.verbose:
[email protected]d90ba3f2010-02-23 14:42:57999 clone_cmd.append('--verbose')
[email protected]3534aa52013-07-20 01:58:081000 clone_cmd.append(url)
[email protected]328c3c72011-06-01 20:50:271001 # If the parent directory does not exist, Git clone on Windows will not
1002 # create it, so we need to do it manually.
1003 parent_dir = os.path.dirname(self.checkout_path)
[email protected]3534aa52013-07-20 01:58:081004 gclient_utils.safe_makedirs(parent_dir)
[email protected]5439ea52014-08-06 17:18:181005
1006 template_dir = None
1007 if hasattr(options, 'no_history') and options.no_history:
John Budorick882c91e2018-07-12 22:11:411008 if gclient_utils.IsGitSha(revision):
[email protected]5439ea52014-08-06 17:18:181009 # In the case of a subproject, the pinned sha is not necessarily the
1010 # head of the remote branch (so we can't just use --depth=N). Instead,
1011 # we tell git to fetch all the remote objects from SHA..HEAD by means of
1012 # a template git dir which has a 'shallow' file pointing to the sha.
1013 template_dir = tempfile.mkdtemp(
1014 prefix='_gclient_gittmp_%s' % os.path.basename(self.checkout_path),
1015 dir=parent_dir)
1016 self._Run(['init', '--bare', template_dir], options, cwd=self._root_dir)
1017 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
1018 template_file.write(revision)
1019 clone_cmd.append('--template=' + template_dir)
1020 else:
1021 # Otherwise, we're just interested in the HEAD. Just use --depth.
1022 clone_cmd.append('--depth=1')
1023
[email protected]3534aa52013-07-20 01:58:081024 tmp_dir = tempfile.mkdtemp(
1025 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
1026 dir=parent_dir)
1027 try:
1028 clone_cmd.append(tmp_dir)
Edward Lemur231f5ea2018-01-31 18:02:361029 if self.print_outbuf:
1030 print_stdout = True
1031 stdout = gclient_utils.WriteToStdout(self.out_fh)
1032 else:
1033 print_stdout = False
1034 stdout = self.out_fh
1035 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True,
1036 print_stdout=print_stdout, stdout=stdout)
[email protected]3534aa52013-07-20 01:58:081037 gclient_utils.safe_makedirs(self.checkout_path)
[email protected]ef509e42013-09-20 13:19:081038 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
1039 os.path.join(self.checkout_path, '.git'))
[email protected]6279e8a2014-02-13 01:45:251040 except:
[email protected]fe0d1902014-04-08 20:50:441041 traceback.print_exc(file=self.out_fh)
[email protected]6279e8a2014-02-13 01:45:251042 raise
[email protected]3534aa52013-07-20 01:58:081043 finally:
1044 if os.listdir(tmp_dir):
[email protected]fe0d1902014-04-08 20:50:441045 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
[email protected]3534aa52013-07-20 01:58:081046 gclient_utils.rmtree(tmp_dir)
[email protected]5439ea52014-08-06 17:18:181047 if template_dir:
1048 gclient_utils.rmtree(template_dir)
Edward Lemur579c9862018-07-13 23:17:511049 self._SetFetchConfig(options)
1050 self._Fetch(options, prune=options.force)
John Budorick882c91e2018-07-12 22:11:411051 revision = self._AutoFetchRef(options, revision)
1052 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
1053 self._Checkout(options, ''.join(remote_ref or revision), quiet=True)
[email protected]483a0ba2014-05-30 00:06:071054 if self._GetCurrentBranch() is None:
[email protected]786fb682010-06-02 15:16:231055 # Squelch git's very verbose detached HEAD warning and use our own
[email protected]fe0d1902014-04-08 20:50:441056 self.Print(
[email protected]d33eab32014-07-07 19:35:181057 ('Checked out %s to a detached HEAD. Before making any commits\n'
1058 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
1059 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
John Budorick882c91e2018-07-12 22:11:411060 'create a new branch for your work.') % (revision, self.remote))
[email protected]d90ba3f2010-02-23 14:42:571061
[email protected]6cd41b62014-04-21 23:55:221062 def _AskForData(self, prompt, options):
[email protected]30c46d62014-01-23 12:11:561063 if options.jobs > 1:
[email protected]6cd41b62014-04-21 23:55:221064 self.Print(prompt)
[email protected]30c46d62014-01-23 12:11:561065 raise gclient_utils.Error("Background task requires input. Rerun "
1066 "gclient with --jobs=1 so that\n"
1067 "interaction is possible.")
1068 try:
1069 return raw_input(prompt)
1070 except KeyboardInterrupt:
1071 # Hide the exception.
1072 sys.exit(1)
1073
1074
[email protected]f5d37bf2010-09-02 00:50:341075 def _AttemptRebase(self, upstream, files, options, newbase=None,
[email protected]30c46d62014-01-23 12:11:561076 branch=None, printed_path=False, merge=False):
[email protected]d90ba3f2010-02-23 14:42:571077 """Attempt to rebase onto either upstream or, if specified, newbase."""
[email protected]396e1a62013-07-03 19:41:041078 if files is not None:
Aaron Gablef4068aa2017-12-12 23:14:091079 files.extend(self._GetDiffFilenames(upstream))
[email protected]d90ba3f2010-02-23 14:42:571080 revision = upstream
1081 if newbase:
1082 revision = newbase
[email protected]30c46d62014-01-23 12:11:561083 action = 'merge' if merge else 'rebase'
[email protected]d90ba3f2010-02-23 14:42:571084 if not printed_path:
[email protected]fe0d1902014-04-08 20:50:441085 self.Print('_____ %s : Attempting %s onto %s...' % (
[email protected]30c46d62014-01-23 12:11:561086 self.relpath, action, revision))
[email protected]d90ba3f2010-02-23 14:42:571087 printed_path = True
1088 else:
[email protected]fe0d1902014-04-08 20:50:441089 self.Print('Attempting %s onto %s...' % (action, revision))
[email protected]30c46d62014-01-23 12:11:561090
1091 if merge:
1092 merge_output = self._Capture(['merge', revision])
1093 if options.verbose:
[email protected]fe0d1902014-04-08 20:50:441094 self.Print(merge_output)
[email protected]30c46d62014-01-23 12:11:561095 return
[email protected]d90ba3f2010-02-23 14:42:571096
1097 # Build the rebase command here using the args
1098 # git rebase [options] [--onto <newbase>] <upstream> [<branch>]
1099 rebase_cmd = ['rebase']
[email protected]f5d37bf2010-09-02 00:50:341100 if options.verbose:
[email protected]d90ba3f2010-02-23 14:42:571101 rebase_cmd.append('--verbose')
1102 if newbase:
1103 rebase_cmd.extend(['--onto', newbase])
1104 rebase_cmd.append(upstream)
1105 if branch:
1106 rebase_cmd.append(branch)
1107
1108 try:
[email protected]ad80e3b2010-09-09 14:18:281109 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
[email protected]bffad372011-09-08 17:54:221110 except subprocess2.CalledProcessError, e:
[email protected]ad80e3b2010-09-09 14:18:281111 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or
1112 re.match(r'cannot rebase: your index contains uncommitted changes',
1113 e.stderr)):
[email protected]d90ba3f2010-02-23 14:42:571114 while True:
[email protected]30c46d62014-01-23 12:11:561115 rebase_action = self._AskForData(
[email protected]90541732011-04-01 17:54:181116 'Cannot rebase because of unstaged changes.\n'
1117 '\'git reset --hard HEAD\' ?\n'
1118 'WARNING: destroys any uncommitted work in your current branch!'
[email protected]18fa4542013-05-21 13:30:461119 ' (y)es / (q)uit / (s)how : ', options)
[email protected]d90ba3f2010-02-23 14:42:571120 if re.match(r'yes|y', rebase_action, re.I):
Robert Iannuccic41d8b92017-02-17 01:07:371121 self._Scrub('HEAD', options)
[email protected]d90ba3f2010-02-23 14:42:571122 # Should this be recursive?
[email protected]ad80e3b2010-09-09 14:18:281123 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path)
[email protected]d90ba3f2010-02-23 14:42:571124 break
1125 elif re.match(r'quit|q', rebase_action, re.I):
1126 raise gclient_utils.Error("Please merge or rebase manually\n"
1127 "cd %s && git " % self.checkout_path
1128 + "%s" % ' '.join(rebase_cmd))
1129 elif re.match(r'show|s', rebase_action, re.I):
[email protected]fe0d1902014-04-08 20:50:441130 self.Print('%s' % e.stderr.strip())
[email protected]d90ba3f2010-02-23 14:42:571131 continue
1132 else:
1133 gclient_utils.Error("Input not recognized")
1134 continue
1135 elif re.search(r'^CONFLICT', e.stdout, re.M):
1136 raise gclient_utils.Error("Conflict while rebasing this branch.\n"
1137 "Fix the conflict and run gclient again.\n"
1138 "See 'man git-rebase' for details.\n")
1139 else:
[email protected]fe0d1902014-04-08 20:50:441140 self.Print(e.stdout.strip())
1141 self.Print('Rebase produced error output:\n%s' % e.stderr.strip())
[email protected]d90ba3f2010-02-23 14:42:571142 raise gclient_utils.Error("Unrecognized error, please merge or rebase "
1143 "manually.\ncd %s && git " %
1144 self.checkout_path
1145 + "%s" % ' '.join(rebase_cmd))
1146
[email protected]fe0d1902014-04-08 20:50:441147 self.Print(rebase_output.strip())
[email protected]f5d37bf2010-09-02 00:50:341148 if not options.verbose:
[email protected]d90ba3f2010-02-23 14:42:571149 # Make the output a little prettier. It's nice to have some
1150 # whitespace between projects when syncing.
[email protected]fe0d1902014-04-08 20:50:441151 self.Print('')
[email protected]d90ba3f2010-02-23 14:42:571152
[email protected]6e29d572010-06-04 17:32:201153 @staticmethod
1154 def _CheckMinVersion(min_version):
[email protected]d0f854a2010-03-11 19:35:531155 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1156 if not ok:
1157 raise gclient_utils.Error('git version %s < minimum required %s' %
1158 (current_version, min_version))
[email protected]923a0372009-12-11 20:42:431159
John Budorick882c91e2018-07-12 22:11:411160 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
[email protected]c438c142015-08-24 22:55:551161 # Special case handling if all 3 conditions are met:
1162 # * the mirros have recently changed, but deps destination remains same,
1163 # * the git histories of mirrors are conflicting.
1164 # * git cache is used
1165 # This manifests itself in current checkout having invalid HEAD commit on
1166 # most git operations. Since git cache is used, just deleted the .git
1167 # folder, and re-create it by cloning.
1168 try:
1169 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1170 except subprocess2.CalledProcessError as e:
1171 if ('fatal: bad object HEAD' in e.stderr
1172 and self.cache_dir and self.cache_dir in url):
1173 self.Print((
1174 'Likely due to DEPS change with git cache_dir, '
1175 'the current commit points to no longer existing object.\n'
1176 '%s' % e)
1177 )
1178 self._DeleteOrMove(options.force)
John Budorick882c91e2018-07-12 22:11:411179 self._Clone(revision, url, options)
[email protected]c438c142015-08-24 22:55:551180 else:
1181 raise
1182
[email protected]786fb682010-06-02 15:16:231183 def _IsRebasing(self):
1184 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1185 # have a plumbing command to determine whether a rebase is in progress, so
1186 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1187 g = os.path.join(self.checkout_path, '.git')
1188 return (
1189 os.path.isdir(os.path.join(g, "rebase-merge")) or
1190 os.path.isdir(os.path.join(g, "rebase-apply")))
1191
Robert Iannuccic41d8b92017-02-17 01:07:371192 def _CheckClean(self, revision, fixup=False):
[email protected]d9b318c2015-12-04 20:03:081193 lockfile = os.path.join(self.checkout_path, ".git", "index.lock")
1194 if os.path.exists(lockfile):
1195 raise gclient_utils.Error(
agable83faed02016-10-24 21:37:101196 '\n____ %s at %s\n'
[email protected]d9b318c2015-12-04 20:03:081197 '\tYour repo is locked, possibly due to a concurrent git process.\n'
1198 '\tIf no git executable is running, then clean up %r and try again.\n'
agable83faed02016-10-24 21:37:101199 % (self.relpath, revision, lockfile))
[email protected]d9b318c2015-12-04 20:03:081200
[email protected]786fb682010-06-02 15:16:231201 # Make sure the tree is clean; see git-rebase.sh for reference
1202 try:
1203 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
[email protected]ad80e3b2010-09-09 14:18:281204 cwd=self.checkout_path)
[email protected]bffad372011-09-08 17:54:221205 except subprocess2.CalledProcessError:
agable83faed02016-10-24 21:37:101206 raise gclient_utils.Error('\n____ %s at %s\n'
[email protected]6e29d572010-06-04 17:32:201207 '\tYou have unstaged changes.\n'
1208 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 21:37:101209 % (self.relpath, revision))
[email protected]786fb682010-06-02 15:16:231210 try:
1211 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
[email protected]27c9c8a2014-09-11 19:57:551212 '--ignore-submodules', 'HEAD', '--'],
[email protected]ad80e3b2010-09-09 14:18:281213 cwd=self.checkout_path)
[email protected]bffad372011-09-08 17:54:221214 except subprocess2.CalledProcessError:
agable83faed02016-10-24 21:37:101215 raise gclient_utils.Error('\n____ %s at %s\n'
[email protected]6e29d572010-06-04 17:32:201216 '\tYour index contains uncommitted changes\n'
1217 '\tPlease commit, stash, or reset.\n'
agable83faed02016-10-24 21:37:101218 % (self.relpath, revision))
[email protected]786fb682010-06-02 15:16:231219
agable83faed02016-10-24 21:37:101220 def _CheckDetachedHead(self, revision, _options):
[email protected]786fb682010-06-02 15:16:231221 # HEAD is detached. Make sure it is safe to move away from (i.e., it is
1222 # reference by a commit). If not, error out -- most likely a rebase is
1223 # in progress, try to detect so we can give a better error.
1224 try:
[email protected]ad80e3b2010-09-09 14:18:281225 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'],
1226 cwd=self.checkout_path)
[email protected]bffad372011-09-08 17:54:221227 except subprocess2.CalledProcessError:
[email protected]786fb682010-06-02 15:16:231228 # Commit is not contained by any rev. See if the user is rebasing:
1229 if self._IsRebasing():
1230 # Punt to the user
agable83faed02016-10-24 21:37:101231 raise gclient_utils.Error('\n____ %s at %s\n'
[email protected]786fb682010-06-02 15:16:231232 '\tAlready in a conflict, i.e. (no branch).\n'
1233 '\tFix the conflict and run gclient again.\n'
1234 '\tOr to abort run:\n\t\tgit-rebase --abort\n'
1235 '\tSee man git-rebase for details.\n'
agable83faed02016-10-24 21:37:101236 % (self.relpath, revision))
[email protected]786fb682010-06-02 15:16:231237 # Let's just save off the commit so we can proceed.
[email protected]6cafa132010-09-07 14:17:261238 name = ('saved-by-gclient-' +
1239 self._Capture(['rev-parse', '--short', 'HEAD']))
[email protected]77bd7362013-09-25 23:46:141240 self._Capture(['branch', '-f', name])
[email protected]fe0d1902014-04-08 20:50:441241 self.Print('_____ found an unreferenced commit and saved it as \'%s\'' %
[email protected]f5d37bf2010-09-02 00:50:341242 name)
[email protected]786fb682010-06-02 15:16:231243
[email protected]5bde4852009-12-14 16:47:121244 def _GetCurrentBranch(self):
[email protected]786fb682010-06-02 15:16:231245 # Returns name of current branch or None for detached HEAD
[email protected]6cafa132010-09-07 14:17:261246 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
[email protected]786fb682010-06-02 15:16:231247 if branch == 'HEAD':
[email protected]5bde4852009-12-14 16:47:121248 return None
1249 return branch
1250
[email protected]c3e09d22014-04-10 13:58:181251 def _Capture(self, args, **kwargs):
[email protected]fe0d1902014-04-08 20:50:441252 kwargs.setdefault('cwd', self.checkout_path)
1253 kwargs.setdefault('stderr', subprocess2.PIPE)
Robert Iannuccia7a9ceb2017-02-17 01:38:061254 strip = kwargs.pop('strip', True)
[email protected]6d8115d2014-04-23 20:59:231255 env = scm.GIT.ApplyEnvVars(kwargs)
Robert Iannuccia7a9ceb2017-02-17 01:38:061256 ret = subprocess2.check_output(['git'] + args, env=env, **kwargs)
1257 if strip:
1258 ret = ret.strip()
1259 return ret
[email protected]6cafa132010-09-07 14:17:261260
[email protected]bb424c02014-06-23 22:42:511261 def _Checkout(self, options, ref, force=False, quiet=None):
1262 """Performs a 'git-checkout' operation.
1263
1264 Args:
1265 options: The configured option set
1266 ref: (str) The branch/commit to checkout
1267 quiet: (bool/None) Whether or not the checkout shoud pass '--quiet'; if
1268 'None', the behavior is inferred from 'options.verbose'.
1269 Returns: (str) The output of the checkout operation
1270 """
1271 if quiet is None:
1272 quiet = (not options.verbose)
1273 checkout_args = ['checkout']
1274 if force:
1275 checkout_args.append('--force')
1276 if quiet:
1277 checkout_args.append('--quiet')
1278 checkout_args.append(ref)
1279 return self._Capture(checkout_args)
1280
Paweł Hajdan, Jr63b8c2a2017-09-05 15:59:081281 def _Fetch(self, options, remote=None, prune=False, quiet=False,
1282 refspec=None):
[email protected]680f2172014-06-25 00:39:321283 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
Edward Lemurd64781e2018-07-11 23:09:551284 # When a mirror is configured, it fetches only the refs/heads, and possibly
1285 # the refs/branch-heads and refs/tags, but not the refs/changes. So, if
1286 # we're asked to fetch a refs/changes ref from the mirror, it won't have it.
1287 # This makes sure that we always fetch refs/changes directly from the
1288 # repository and not from the mirror.
1289 if refspec and refspec.startswith('refs/changes'):
1290 remote, _ = gclient_utils.SplitUrlRevision(self.url)
1291 # Make sure that we fetch the (remote) refs/changes/xx ref to the (local)
1292 # refs/changes/xx ref.
1293 if ':' not in refspec:
1294 refspec += ':' + refspec
[email protected]680f2172014-06-25 00:39:321295 fetch_cmd = cfg + [
1296 'fetch',
1297 remote or self.remote,
1298 ]
Paweł Hajdan, Jr63b8c2a2017-09-05 15:59:081299 if refspec:
1300 fetch_cmd.append(refspec)
[email protected]680f2172014-06-25 00:39:321301
1302 if prune:
1303 fetch_cmd.append('--prune')
1304 if options.verbose:
1305 fetch_cmd.append('--verbose')
1306 elif quiet:
1307 fetch_cmd.append('--quiet')
tandrii64103db2016-10-11 12:30:051308 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
[email protected]680f2172014-06-25 00:39:321309
1310 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1311 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1312
Edward Lemur579c9862018-07-13 23:17:511313 def _SetFetchConfig(self, options):
[email protected]8d3348f2014-08-19 22:49:161314 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
1315 if requested."""
Edward Lemur2f38df62018-07-14 02:13:211316 if options.force or options.reset:
Edward Lemur579c9862018-07-13 23:17:511317 try:
1318 self._Run(['config', '--unset-all', 'remote.%s.fetch' % self.remote],
1319 options)
1320 self._Run(['config', 'remote.%s.fetch' % self.remote,
1321 '+refs/heads/*:refs/remotes/%s/*' % self.remote], options)
1322 except subprocess2.CalledProcessError as e:
1323 # If exit code was 5, it means we attempted to unset a config that
1324 # didn't exist. Ignore it.
1325 if e.returncode != 5:
1326 raise
[email protected]e409df62013-04-16 17:28:571327 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
[email protected]1a60dca2013-11-26 14:06:261328 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
[email protected]f2d7d6b2013-10-17 20:41:431329 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1330 '^\\+refs/branch-heads/\\*:.*$']
1331 self._Run(config_cmd, options)
[email protected]8d3348f2014-08-19 22:49:161332 if hasattr(options, 'with_tags') and options.with_tags:
1333 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1334 '+refs/tags/*:refs/tags/*',
1335 '^\\+refs/tags/\\*:.*$']
1336 self._Run(config_cmd, options)
[email protected]e409df62013-04-16 17:28:571337
John Budorick882c91e2018-07-12 22:11:411338 def _AutoFetchRef(self, options, revision):
Paweł Hajdan, Jr63b8c2a2017-09-05 15:59:081339 """Attempts to fetch |revision| if not available in local repo.
1340
1341 Returns possibly updated revision."""
John Budorick882c91e2018-07-12 22:11:411342 try:
1343 self._Capture(['rev-parse', revision])
1344 except subprocess2.CalledProcessError:
Paweł Hajdan, Jr63b8c2a2017-09-05 15:59:081345 self._Fetch(options, refspec=revision)
1346 revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
1347 return revision
1348
[email protected]680f2172014-06-25 00:39:321349 def _Run(self, args, options, show_header=True, **kwargs):
Quinten Yearsleyb2cc4a92016-12-15 21:53:261350 # Disable 'unused options' warning | pylint: disable=unused-argument
[email protected]2cd0b8e2014-09-22 21:17:591351 kwargs.setdefault('cwd', self.checkout_path)
[email protected]fe0d1902014-04-08 20:50:441352 kwargs.setdefault('stdout', self.out_fh)
[email protected]848fd492014-04-09 19:06:441353 kwargs['filter_fn'] = self.filter
[email protected]fe0d1902014-04-08 20:50:441354 kwargs.setdefault('print_stdout', False)
[email protected]6d8115d2014-04-23 20:59:231355 env = scm.GIT.ApplyEnvVars(kwargs)
[email protected]772efaf2014-04-01 02:35:441356 cmd = ['git'] + args
[email protected]680f2172014-06-25 00:39:321357 if show_header:
[email protected]2cd0b8e2014-09-22 21:17:591358 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1359 else:
1360 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
John Budorick0f7b2002018-01-19 23:46:171361
1362
1363class CipdPackage(object):
1364 """A representation of a single CIPD package."""
1365
John Budorickd3ba72b2018-03-20 19:27:421366 def __init__(self, name, version, authority_for_subdir):
John Budorick0f7b2002018-01-19 23:46:171367 self._authority_for_subdir = authority_for_subdir
1368 self._name = name
1369 self._version = version
1370
1371 @property
John Budorick0f7b2002018-01-19 23:46:171372 def authority_for_subdir(self):
1373 """Whether this package has authority to act on behalf of its subdir.
1374
1375 Some operations should only be performed once per subdirectory. A package
1376 that has authority for its subdirectory is the only package that should
1377 perform such operations.
1378
1379 Returns:
1380 bool; whether this package has subdir authority.
1381 """
1382 return self._authority_for_subdir
1383
1384 @property
1385 def name(self):
1386 return self._name
1387
1388 @property
1389 def version(self):
1390 return self._version
1391
1392
1393class CipdRoot(object):
1394 """A representation of a single CIPD root."""
1395 def __init__(self, root_dir, service_url):
1396 self._all_packages = set()
1397 self._mutator_lock = threading.Lock()
1398 self._packages_by_subdir = collections.defaultdict(list)
1399 self._root_dir = root_dir
1400 self._service_url = service_url
1401
1402 def add_package(self, subdir, package, version):
1403 """Adds a package to this CIPD root.
1404
1405 As far as clients are concerned, this grants both root and subdir authority
1406 to packages arbitrarily. (The implementation grants root authority to the
1407 first package added and subdir authority to the first package added for that
1408 subdir, but clients should not depend on or expect that behavior.)
1409
1410 Args:
1411 subdir: str; relative path to where the package should be installed from
1412 the cipd root directory.
1413 package: str; the cipd package name.
1414 version: str; the cipd package version.
1415 Returns:
1416 CipdPackage; the package that was created and added to this root.
1417 """
1418 with self._mutator_lock:
1419 cipd_package = CipdPackage(
1420 package, version,
John Budorick0f7b2002018-01-19 23:46:171421 not self._packages_by_subdir[subdir])
1422 self._all_packages.add(cipd_package)
1423 self._packages_by_subdir[subdir].append(cipd_package)
1424 return cipd_package
1425
1426 def packages(self, subdir):
1427 """Get the list of configured packages for the given subdir."""
1428 return list(self._packages_by_subdir[subdir])
1429
1430 def clobber(self):
1431 """Remove the .cipd directory.
1432
1433 This is useful for forcing ensure to redownload and reinitialize all
1434 packages.
1435 """
1436 with self._mutator_lock:
John Budorickd3ba72b2018-03-20 19:27:421437 cipd_cache_dir = os.path.join(self.root_dir, '.cipd')
John Budorick0f7b2002018-01-19 23:46:171438 try:
1439 gclient_utils.rmtree(os.path.join(cipd_cache_dir))
1440 except OSError:
1441 if os.path.exists(cipd_cache_dir):
1442 raise
1443
1444 @contextlib.contextmanager
1445 def _create_ensure_file(self):
1446 try:
1447 ensure_file = None
1448 with tempfile.NamedTemporaryFile(
1449 suffix='.ensure', delete=False) as ensure_file:
John Budorick302bb842018-07-17 23:49:171450 ensure_file.write('$ParanoidMode CheckPresence\n\n')
John Budorick0f7b2002018-01-19 23:46:171451 for subdir, packages in sorted(self._packages_by_subdir.iteritems()):
1452 ensure_file.write('@Subdir %s\n' % subdir)
Edward Lemurfbb06aa2018-06-11 20:43:061453 for package in sorted(packages, key=lambda p: p.name):
John Budorick0f7b2002018-01-19 23:46:171454 ensure_file.write('%s %s\n' % (package.name, package.version))
1455 ensure_file.write('\n')
1456 yield ensure_file.name
1457 finally:
1458 if ensure_file is not None and os.path.exists(ensure_file.name):
1459 os.remove(ensure_file.name)
1460
1461 def ensure(self):
1462 """Run `cipd ensure`."""
1463 with self._mutator_lock:
1464 with self._create_ensure_file() as ensure_file:
1465 cmd = [
1466 'cipd', 'ensure',
1467 '-log-level', 'error',
1468 '-root', self.root_dir,
1469 '-ensure-file', ensure_file,
1470 ]
1471 gclient_utils.CheckCallAndFilterAndHeader(cmd)
1472
John Budorickd3ba72b2018-03-20 19:27:421473 def run(self, command):
1474 if command == 'update':
1475 self.ensure()
1476 elif command == 'revert':
1477 self.clobber()
1478 self.ensure()
1479
John Budorick0f7b2002018-01-19 23:46:171480 def created_package(self, package):
1481 """Checks whether this root created the given package.
1482
1483 Args:
1484 package: CipdPackage; the package to check.
1485 Returns:
1486 bool; whether this root created the given package.
1487 """
1488 return package in self._all_packages
1489
1490 @property
1491 def root_dir(self):
1492 return self._root_dir
1493
1494 @property
1495 def service_url(self):
1496 return self._service_url
1497
1498
1499class CipdWrapper(SCMWrapper):
1500 """Wrapper for CIPD.
1501
1502 Currently only supports chrome-infra-packages.appspot.com.
1503 """
John Budorick3929e9e2018-02-05 02:18:071504 name = 'cipd'
John Budorick0f7b2002018-01-19 23:46:171505
1506 def __init__(self, url=None, root_dir=None, relpath=None, out_fh=None,
1507 out_cb=None, root=None, package=None):
1508 super(CipdWrapper, self).__init__(
1509 url=url, root_dir=root_dir, relpath=relpath, out_fh=out_fh,
1510 out_cb=out_cb)
1511 assert root.created_package(package)
1512 self._package = package
1513 self._root = root
1514
1515 #override
1516 def GetCacheMirror(self):
1517 return None
1518
1519 #override
1520 def GetActualRemoteURL(self, options):
1521 return self._root.service_url
1522
1523 #override
1524 def DoesRemoteURLMatch(self, options):
1525 del options
1526 return True
1527
1528 def revert(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 19:27:421529 """Does nothing.
1530
1531 CIPD packages should be reverted at the root by running
1532 `CipdRoot.run('revert')`.
1533 """
1534 pass
John Budorick0f7b2002018-01-19 23:46:171535
1536 def diff(self, options, args, file_list):
1537 """CIPD has no notion of diffing."""
1538 pass
1539
1540 def pack(self, options, args, file_list):
1541 """CIPD has no notion of diffing."""
1542 pass
1543
1544 def revinfo(self, options, args, file_list):
1545 """Grab the instance ID."""
1546 try:
1547 tmpdir = tempfile.mkdtemp()
1548 describe_json_path = os.path.join(tmpdir, 'describe.json')
1549 cmd = [
1550 'cipd', 'describe',
1551 self._package.name,
1552 '-log-level', 'error',
1553 '-version', self._package.version,
1554 '-json-output', describe_json_path
1555 ]
1556 gclient_utils.CheckCallAndFilter(
1557 cmd, filter_fn=lambda _line: None, print_stdout=False)
1558 with open(describe_json_path) as f:
1559 describe_json = json.load(f)
1560 return describe_json.get('result', {}).get('pin', {}).get('instance_id')
1561 finally:
1562 gclient_utils.rmtree(tmpdir)
1563
1564 def status(self, options, args, file_list):
1565 pass
1566
1567 def update(self, options, args, file_list):
John Budorickd3ba72b2018-03-20 19:27:421568 """Does nothing.
1569
1570 CIPD packages should be updated at the root by running
1571 `CipdRoot.run('update')`.
1572 """
1573 pass