| OLD | NEW |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Gclient-specific SCM-specific operations.""" | 5 """Gclient-specific SCM-specific operations.""" |
| 6 | 6 |
| 7 import collections | |
| 8 import logging | 7 import logging |
| 9 import os | 8 import os |
| 10 import posixpath | 9 import posixpath |
| 11 import re | 10 import re |
| 12 import sys | 11 import sys |
| 13 import tempfile | 12 import tempfile |
| 14 import threading | |
| 15 import traceback | 13 import traceback |
| 16 import urlparse | 14 import urlparse |
| 17 | 15 |
| 18 import download_from_google_storage | 16 import download_from_google_storage |
| 19 import gclient_utils | 17 import gclient_utils |
| 20 import scm | 18 import scm |
| 21 import subprocess2 | 19 import subprocess2 |
| 22 | 20 |
| 23 | 21 |
| 24 THIS_FILE_PATH = os.path.abspath(__file__) | 22 THIS_FILE_PATH = os.path.abspath(__file__) |
| (...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 141 | 139 |
| 142 return getattr(self, command)(options, args, file_list) | 140 return getattr(self, command)(options, args, file_list) |
| 143 | 141 |
| 144 | 142 |
| 145 class GitWrapper(SCMWrapper): | 143 class GitWrapper(SCMWrapper): |
| 146 """Wrapper for Git""" | 144 """Wrapper for Git""" |
| 147 name = 'git' | 145 name = 'git' |
| 148 remote = 'origin' | 146 remote = 'origin' |
| 149 | 147 |
| 150 cache_dir = None | 148 cache_dir = None |
| 151 # If a given cache is used in a solution more than once, prevent multiple | |
| 152 # threads from updating it simultaneously. | |
| 153 cache_locks = collections.defaultdict(threading.Lock) | |
| 154 | 149 |
| 155 def __init__(self, url=None, root_dir=None, relpath=None): | 150 def __init__(self, url=None, root_dir=None, relpath=None): |
| 156 """Removes 'git+' fake prefix from git URL.""" | 151 """Removes 'git+' fake prefix from git URL.""" |
| 157 if url.startswith('git+http://') or url.startswith('git+https://'): | 152 if url.startswith('git+http://') or url.startswith('git+https://'): |
| 158 url = url[4:] | 153 url = url[4:] |
| 159 SCMWrapper.__init__(self, url, root_dir, relpath) | 154 SCMWrapper.__init__(self, url, root_dir, relpath) |
| 160 | 155 |
| 161 @staticmethod | 156 @staticmethod |
| 162 def BinaryExists(): | 157 def BinaryExists(): |
| 163 """Returns true if the command exists.""" | 158 """Returns true if the command exists.""" |
| (...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 345 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote], | 340 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote], |
| 346 cwd=self.checkout_path).strip() != 'False'): | 341 cwd=self.checkout_path).strip() != 'False'): |
| 347 print('_____ switching %s to a new upstream' % self.relpath) | 342 print('_____ switching %s to a new upstream' % self.relpath) |
| 348 # Make sure it's clean | 343 # Make sure it's clean |
| 349 self._CheckClean(rev_str) | 344 self._CheckClean(rev_str) |
| 350 # Switch over to the new upstream | 345 # Switch over to the new upstream |
| 351 self._Run(['remote', 'set-url', self.remote, url], options) | 346 self._Run(['remote', 'set-url', self.remote, url], options) |
| 352 self._FetchAndReset(revision, file_list, options) | 347 self._FetchAndReset(revision, file_list, options) |
| 353 return_early = True | 348 return_early = True |
| 354 | 349 |
| 355 # Need to do this in the normal path as well as in the post-remote-switch | |
| 356 # path. | |
| 357 self._PossiblySwitchCache(url, options) | |
| 358 | |
| 359 if return_early: | 350 if return_early: |
| 360 return self._Capture(['rev-parse', '--verify', 'HEAD']) | 351 return self._Capture(['rev-parse', '--verify', 'HEAD']) |
| 361 | 352 |
| 362 cur_branch = self._GetCurrentBranch() | 353 cur_branch = self._GetCurrentBranch() |
| 363 | 354 |
| 364 # Cases: | 355 # Cases: |
| 365 # 0) HEAD is detached. Probably from our initial clone. | 356 # 0) HEAD is detached. Probably from our initial clone. |
| 366 # - make sure HEAD is contained by a named ref, then update. | 357 # - make sure HEAD is contained by a named ref, then update. |
| 367 # Cases 1-4. HEAD is a branch. | 358 # Cases 1-4. HEAD is a branch. |
| 368 # 1) current branch is not tracking a remote branch (could be git-svn) | 359 # 1) current branch is not tracking a remote branch (could be git-svn) |
| (...skipping 305 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 674 '#Initial_checkout' ) % rev) | 665 '#Initial_checkout' ) % rev) |
| 675 | 666 |
| 676 return sha1 | 667 return sha1 |
| 677 | 668 |
| 678 def FullUrlForRelativeUrl(self, url): | 669 def FullUrlForRelativeUrl(self, url): |
| 679 # Strip from last '/' | 670 # Strip from last '/' |
| 680 # Equivalent to unix basename | 671 # Equivalent to unix basename |
| 681 base_url = self.url | 672 base_url = self.url |
| 682 return base_url[:base_url.rfind('/')] + url | 673 return base_url[:base_url.rfind('/')] + url |
| 683 | 674 |
| 684 @staticmethod | |
| 685 def _NormalizeGitURL(url): | |
| 686 '''Takes a git url, strips the scheme, and ensures it ends with '.git'.''' | |
| 687 idx = url.find('://') | |
| 688 if idx != -1: | |
| 689 url = url[idx+3:] | |
| 690 if not url.endswith('.git'): | |
| 691 url += '.git' | |
| 692 return url | |
| 693 | |
| 694 def _PossiblySwitchCache(self, url, options): | |
| 695 """Handles switching a repo from with-cache to direct, or vice versa. | |
| 696 | |
| 697 When we go from direct to with-cache, the remote url changes from the | |
| 698 'real' url to the local file url (in cache_dir). Therefore, this function | |
| 699 assumes that |url| points to the correctly-switched-over local file url, if | |
| 700 we're in cache_mode. | |
| 701 | |
| 702 When we go from with-cache to direct, assume that the normal url-switching | |
| 703 code already flipped the remote over, and we just need to repack and break | |
| 704 the dependency to the cache. | |
| 705 """ | |
| 706 | |
| 707 altfile = os.path.join( | |
| 708 self.checkout_path, '.git', 'objects', 'info', 'alternates') | |
| 709 if self.cache_dir: | |
| 710 if not os.path.exists(altfile): | |
| 711 try: | |
| 712 with open(altfile, 'w') as f: | |
| 713 f.write(os.path.join(url, 'objects')) | |
| 714 # pylint: disable=C0301 | |
| 715 # This dance is necessary according to emperical evidence, also at: | |
| 716 # http://lists-archives.com/git/713652-retrospectively-add-alternates-
to-a-repository.html | |
| 717 self._Run(['repack', '-ad'], options) | |
| 718 self._Run(['repack', '-adl'], options) | |
| 719 except Exception: | |
| 720 # If something goes wrong, try to remove the altfile so we'll go down | |
| 721 # this path again next time. | |
| 722 try: | |
| 723 os.remove(altfile) | |
| 724 except OSError as e: | |
| 725 print >> sys.stderr, "FAILED: os.remove('%s') -> %s" % (altfile, e) | |
| 726 raise | |
| 727 else: | |
| 728 if os.path.exists(altfile): | |
| 729 self._Run(['repack', '-a'], options) | |
| 730 os.remove(altfile) | |
| 731 | |
| 732 def _CreateOrUpdateCache(self, url, options): | 675 def _CreateOrUpdateCache(self, url, options): |
| 733 """Make a new git mirror or update existing mirror for |url|, and return the | 676 """Make a new git mirror or update existing mirror for |url|, and return the |
| 734 mirror URI to clone from. | 677 mirror URI to clone from. |
| 735 | 678 |
| 736 If no cache-dir is specified, just return |url| unchanged. | 679 If no cache-dir is specified, just return |url| unchanged. |
| 737 """ | 680 """ |
| 738 if not self.cache_dir: | 681 if not self.cache_dir: |
| 739 return url | 682 return url |
| 740 | |
| 741 # Replace - with -- to avoid ambiguity. / with - to flatten folder structure | |
| 742 folder = os.path.join( | |
| 743 self.cache_dir, | |
| 744 self._NormalizeGitURL(url).replace('-', '--').replace('/', '-')) | |
| 745 altfile = os.path.join(folder, 'objects', 'info', 'alternates') | |
| 746 | |
| 747 # If we're bringing an old cache up to date or cloning a new cache, and the | |
| 748 # existing repo is currently a direct clone, use its objects to help out | |
| 749 # the fetch here. | |
| 750 checkout_objects = os.path.join(self.checkout_path, '.git', 'objects') | |
| 751 checkout_altfile = os.path.join(checkout_objects, 'info', 'alternates') | |
| 752 use_reference = ( | |
| 753 os.path.exists(checkout_objects) and | |
| 754 not os.path.exists(checkout_altfile)) | |
| 755 | |
| 756 v = ['-v'] if options.verbose else [] | 683 v = ['-v'] if options.verbose else [] |
| 757 filter_fn = lambda l: '[up to date]' not in l | 684 self._Run(['cache', 'populate'] + v + |
| 758 with self.cache_locks[folder]: | 685 ['--shallow', '--cache-dir', self.cache_dir, url], |
| 759 gclient_utils.safe_makedirs(self.cache_dir) | 686 options, cwd=self._root_dir, retry=True) |
| 760 if not os.path.exists(os.path.join(folder, 'config')): | 687 return self._Run(['cache', 'exists', '--cache-dir', self.cache_dir, url], |
| 761 gclient_utils.rmtree(folder) | 688 options).strip() |
| 762 cmd = ['clone'] + v + ['-c', 'core.deltaBaseCacheLimit=2g', | |
| 763 '--progress', '--bare'] | |
| 764 | |
| 765 if use_reference: | |
| 766 cmd += ['--reference', os.path.abspath(self.checkout_path)] | |
| 767 | |
| 768 self._Run(cmd + [url, folder], | |
| 769 options, filter_fn=filter_fn, cwd=self.cache_dir, retry=True) | |
| 770 else: | |
| 771 # For now, assert that host/path/to/repo.git is identical. We may want | |
| 772 # to relax this restriction in the future to allow for smarter cache | |
| 773 # repo update schemes (such as pulling the same repo, but from a | |
| 774 # different host). | |
| 775 existing_url = self._Capture(['config', 'remote.%s.url' % self.remote], | |
| 776 cwd=folder) | |
| 777 assert self._NormalizeGitURL(existing_url) == self._NormalizeGitURL(url) | |
| 778 | |
| 779 if use_reference: | |
| 780 with open(altfile, 'w') as f: | |
| 781 f.write(os.path.abspath(checkout_objects)) | |
| 782 | |
| 783 # Would normally use `git remote update`, but it doesn't support | |
| 784 # --progress, so use fetch instead. | |
| 785 self._Run(['fetch'] + v + ['--multiple', '--progress', '--all'], | |
| 786 options, filter_fn=filter_fn, cwd=folder, retry=True) | |
| 787 | |
| 788 # If the clone has an object dependency on the existing repo, break it | |
| 789 # with repack and remove the linkage. | |
| 790 if os.path.exists(altfile): | |
| 791 self._Run(['repack', '-a'], options, cwd=folder) | |
| 792 os.remove(altfile) | |
| 793 return folder | |
| 794 | 689 |
| 795 def _Clone(self, revision, url, options): | 690 def _Clone(self, revision, url, options): |
| 796 """Clone a git repository from the given URL. | 691 """Clone a git repository from the given URL. |
| 797 | 692 |
| 798 Once we've cloned the repo, we checkout a working branch if the specified | 693 Once we've cloned the repo, we checkout a working branch if the specified |
| 799 revision is a branch head. If it is a tag or a specific commit, then we | 694 revision is a branch head. If it is a tag or a specific commit, then we |
| 800 leave HEAD detached as it makes future updates simpler -- in this case the | 695 leave HEAD detached as it makes future updates simpler -- in this case the |
| 801 user should first create a new branch or switch to an existing branch before | 696 user should first create a new branch or switch to an existing branch before |
| 802 making changes in the repo.""" | 697 making changes in the repo.""" |
| 803 if not options.verbose: | 698 if not options.verbose: |
| (...skipping 233 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1037 # password prompt and simply allow git to fail noisily. The error | 932 # password prompt and simply allow git to fail noisily. The error |
| 1038 # message produced by git will be copied to gclient's output. | 933 # message produced by git will be copied to gclient's output. |
| 1039 env = kwargs.get('env') or kwargs.setdefault('env', os.environ.copy()) | 934 env = kwargs.get('env') or kwargs.setdefault('env', os.environ.copy()) |
| 1040 env.setdefault('GIT_ASKPASS', 'true') | 935 env.setdefault('GIT_ASKPASS', 'true') |
| 1041 env.setdefault('SSH_ASKPASS', 'true') | 936 env.setdefault('SSH_ASKPASS', 'true') |
| 1042 else: | 937 else: |
| 1043 kwargs.setdefault('print_stdout', True) | 938 kwargs.setdefault('print_stdout', True) |
| 1044 stdout = kwargs.get('stdout', sys.stdout) | 939 stdout = kwargs.get('stdout', sys.stdout) |
| 1045 stdout.write('\n________ running \'git %s\' in \'%s\'\n' % ( | 940 stdout.write('\n________ running \'git %s\' in \'%s\'\n' % ( |
| 1046 ' '.join(args), kwargs['cwd'])) | 941 ' '.join(args), kwargs['cwd'])) |
| 1047 gclient_utils.CheckCallAndFilter(['git'] + args, **kwargs) | 942 return gclient_utils.CheckCallAndFilter(['git'] + args, **kwargs) |
| 1048 | 943 |
| 1049 | 944 |
| 1050 class SVNWrapper(SCMWrapper): | 945 class SVNWrapper(SCMWrapper): |
| 1051 """ Wrapper for SVN """ | 946 """ Wrapper for SVN """ |
| 1052 name = 'svn' | 947 name = 'svn' |
| 1053 | 948 |
| 1054 @staticmethod | 949 @staticmethod |
| 1055 def BinaryExists(): | 950 def BinaryExists(): |
| 1056 """Returns true if the command exists.""" | 951 """Returns true if the command exists.""" |
| 1057 try: | 952 try: |
| (...skipping 448 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1506 new_command.append('--force') | 1401 new_command.append('--force') |
| 1507 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1402 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
| 1508 new_command.extend(('--accept', 'theirs-conflict')) | 1403 new_command.extend(('--accept', 'theirs-conflict')) |
| 1509 elif options.manually_grab_svn_rev: | 1404 elif options.manually_grab_svn_rev: |
| 1510 new_command.append('--force') | 1405 new_command.append('--force') |
| 1511 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1406 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
| 1512 new_command.extend(('--accept', 'postpone')) | 1407 new_command.extend(('--accept', 'postpone')) |
| 1513 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1408 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
| 1514 new_command.extend(('--accept', 'postpone')) | 1409 new_command.extend(('--accept', 'postpone')) |
| 1515 return new_command | 1410 return new_command |
| OLD | NEW |