Chromium Code Reviews| 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 logging | 7 import logging |
| 8 import os | 8 import os |
| 9 import posixpath | 9 import posixpath |
| 10 import re | 10 import re |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 145 if not command in commands: | 145 if not command in commands: |
| 146 raise gclient_utils.Error('Unknown command %s' % command) | 146 raise gclient_utils.Error('Unknown command %s' % command) |
| 147 | 147 |
| 148 if not command in dir(self): | 148 if not command in dir(self): |
| 149 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % ( | 149 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % ( |
| 150 command, self.__class__.__name__)) | 150 command, self.__class__.__name__)) |
| 151 | 151 |
| 152 return getattr(self, command)(options, args, file_list) | 152 return getattr(self, command)(options, args, file_list) |
| 153 | 153 |
| 154 | 154 |
| 155 class GitFilter(object): | |
| 156 PERCENT_RE = re.compile('.* ([0-9]{1,2})% .*') | |
| 157 | |
| 158 def __init__(self, nag_timer, predicate=None): | |
| 159 """Create a new GitFilter. | |
|
Michael Moss
2013/07/02 20:47:52
I tend to overdo code comments, but even I'm offen
iannucci
2013/07/02 22:10:07
Yeah, the state of docstrings in this file is fair
| |
| 160 | |
| 161 Args: | |
|
Michael Moss
2013/07/02 20:47:52
When documenting args, we generally document them
iannucci
2013/07/02 22:10:07
Oops! Done.
| |
| 162 predicate (f(line)): An optional function which is invoked for every line. | |
| 163 The line will be skipped if the predicate is False. | |
|
Michael Moss
2013/07/02 20:47:52
At first I read this as "if predicate == False". P
iannucci
2013/07/02 22:10:07
Done.
| |
| 164 """ | |
| 165 self.last_time = 0 | |
| 166 self.nag_timer = nag_timer | |
| 167 self.predicate = predicate | |
| 168 | |
| 169 def __call__(self, line): | |
| 170 # git uses an escape sequence to clear the line; elide it. | |
| 171 esc = line.find(unichr(033)) | |
| 172 if esc > -1: | |
| 173 line = line[:esc] | |
| 174 if self.predicate and not self.predicate(line): | |
| 175 return | |
| 176 now = time.time() | |
| 177 match = self.PERCENT_RE.match(line) | |
| 178 if not match: | |
| 179 self.last_time = 0 | |
| 180 if (now - self.last_time) >= (self.nag_timer / 2): | |
| 181 self.last_time = now | |
| 182 print line | |
| 183 | |
| 184 | |
| 155 class GitWrapper(SCMWrapper): | 185 class GitWrapper(SCMWrapper): |
| 156 """Wrapper for Git""" | 186 """Wrapper for Git""" |
| 157 | 187 |
| 158 def __init__(self, url=None, root_dir=None, relpath=None): | 188 def __init__(self, url=None, root_dir=None, relpath=None): |
| 159 """Removes 'git+' fake prefix from git URL.""" | 189 """Removes 'git+' fake prefix from git URL.""" |
| 160 if url.startswith('git+http://') or url.startswith('git+https://'): | 190 if url.startswith('git+http://') or url.startswith('git+https://'): |
| 161 url = url[4:] | 191 url = url[4:] |
| 162 SCMWrapper.__init__(self, url, root_dir, relpath) | 192 SCMWrapper.__init__(self, url, root_dir, relpath) |
| 163 | 193 |
| 164 @staticmethod | 194 @staticmethod |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 290 rev_str = ' at %s' % revision | 320 rev_str = ' at %s' % revision |
| 291 files = [] | 321 files = [] |
| 292 | 322 |
| 293 printed_path = False | 323 printed_path = False |
| 294 verbose = [] | 324 verbose = [] |
| 295 if options.verbose: | 325 if options.verbose: |
| 296 print('\n_____ %s%s' % (self.relpath, rev_str)) | 326 print('\n_____ %s%s' % (self.relpath, rev_str)) |
| 297 verbose = ['--verbose'] | 327 verbose = ['--verbose'] |
| 298 printed_path = True | 328 printed_path = True |
| 299 | 329 |
| 330 url = self._CreateOrUpdateCache(url, options) | |
| 331 | |
| 300 if revision.startswith('refs/heads/'): | 332 if revision.startswith('refs/heads/'): |
| 301 rev_type = "branch" | 333 rev_type = "branch" |
| 302 elif revision.startswith('origin/'): | 334 elif revision.startswith('origin/'): |
| 303 # For compatability with old naming, translate 'origin' to 'refs/heads' | 335 # For compatability with old naming, translate 'origin' to 'refs/heads' |
| 304 revision = revision.replace('origin/', 'refs/heads/') | 336 revision = revision.replace('origin/', 'refs/heads/') |
| 305 rev_type = "branch" | 337 rev_type = "branch" |
| 306 else: | 338 else: |
| 307 # hash is also a tag, only make a distinction at checkout | 339 # hash is also a tag, only make a distinction at checkout |
| 308 rev_type = "hash" | 340 rev_type = "hash" |
| 309 | 341 |
| (...skipping 374 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 684 '#Initial_checkout' ) % rev) | 716 '#Initial_checkout' ) % rev) |
| 685 | 717 |
| 686 return sha1 | 718 return sha1 |
| 687 | 719 |
| 688 def FullUrlForRelativeUrl(self, url): | 720 def FullUrlForRelativeUrl(self, url): |
| 689 # Strip from last '/' | 721 # Strip from last '/' |
| 690 # Equivalent to unix basename | 722 # Equivalent to unix basename |
| 691 base_url = self.url | 723 base_url = self.url |
| 692 return base_url[:base_url.rfind('/')] + url | 724 return base_url[:base_url.rfind('/')] + url |
| 693 | 725 |
| 726 @staticmethod | |
| 727 def _CacheFolder(url, options): | |
| 728 """Transforms a url into the path to the corresponding git cache. | |
| 729 | |
| 730 Ex. (assuming cache_dir == '/cache') | |
| 731 IN: https://chromium.googlesource.com/chromium/src | |
| 732 OUT: /cache/chromium.googlesource.com-chromium-src.git | |
| 733 """ | |
| 734 idx = url.find('://') | |
| 735 if idx != -1: | |
| 736 url = url[idx+3:] | |
| 737 # Replace - with -- to avoid ambiguity. / with - to flatten folder structure | |
| 738 url = url.replace('-', '--').replace('/', '-') | |
| 739 if not url.endswith('.git'): | |
| 740 url += '.git' | |
| 741 return os.path.join(options.cache_dir, url) | |
| 742 | |
| 743 def _CreateOrUpdateCache(self, url, options): | |
| 744 """Make a new git mirror or update existing mirror for |url|, and return the | |
| 745 mirror URI to clone from. | |
| 746 | |
| 747 If no cache-dir is specified, just return |url| unchanged. | |
| 748 """ | |
| 749 if not options.cache_dir: | |
| 750 return url | |
| 751 folder = self._CacheFolder(url, options) | |
| 752 v = ['-v'] if options.verbose else [] | |
| 753 filter_fn = lambda l: '[up to date]' not in l | |
| 754 with options.cache_locks[folder]: | |
| 755 gclient_utils.safe_makedirs(options.cache_dir) | |
| 756 if not os.path.exists(os.path.join(folder, 'config')): | |
| 757 gclient_utils.rmtree(folder) | |
| 758 self._Run(['clone'] + v + ['-c', 'core.deltaBaseCacheLimit=2g', | |
| 759 '--progress', '--mirror', url, folder], | |
| 760 options, git_filter=True, filter_fn=filter_fn, | |
| 761 cwd=options.cache_dir) | |
| 762 else: | |
| 763 # Would normally use `git remote update`, but it doesn't support | |
| 764 # --progress, so use fetch instead. | |
|
szager1
2013/07/02 20:58:21
existing_url = self._Run(['config', 'remote.origin
iannucci
2013/07/02 22:10:07
Why? The premise is that it doesn't matter what ac
szager1
2013/07/02 22:14:05
This is the collision-detection logic. Maybe stri
| |
| 765 self._Run(['fetch'] + v + ['--multiple', '--progress', '--all'], | |
| 766 options, git_filter=True, filter_fn=filter_fn, cwd=folder) | |
| 767 return folder | |
| 768 | |
| 694 def _Clone(self, revision, url, options): | 769 def _Clone(self, revision, url, options): |
| 695 """Clone a git repository from the given URL. | 770 """Clone a git repository from the given URL. |
| 696 | 771 |
| 697 Once we've cloned the repo, we checkout a working branch if the specified | 772 Once we've cloned the repo, we checkout a working branch if the specified |
| 698 revision is a branch head. If it is a tag or a specific commit, then we | 773 revision is a branch head. If it is a tag or a specific commit, then we |
| 699 leave HEAD detached as it makes future updates simpler -- in this case the | 774 leave HEAD detached as it makes future updates simpler -- in this case the |
| 700 user should first create a new branch or switch to an existing branch before | 775 user should first create a new branch or switch to an existing branch before |
| 701 making changes in the repo.""" | 776 making changes in the repo.""" |
| 702 if not options.verbose: | 777 if not options.verbose: |
| 703 # git clone doesn't seem to insert a newline properly before printing | 778 # git clone doesn't seem to insert a newline properly before printing |
| 704 # to stdout | 779 # to stdout |
| 705 print('') | 780 print('') |
| 706 clone_cmd = ['-c', 'core.deltaBaseCacheLimit=2g', 'clone', '--progress'] | 781 clone_cmd = ['-c', 'core.deltaBaseCacheLimit=2g', 'clone', '--progress'] |
| 782 if options.cache_dir: | |
| 783 clone_cmd.append('--shared') | |
| 707 if revision.startswith('refs/heads/'): | 784 if revision.startswith('refs/heads/'): |
| 708 clone_cmd.extend(['-b', revision.replace('refs/heads/', '')]) | 785 clone_cmd.extend(['-b', revision.replace('refs/heads/', '')]) |
| 709 detach_head = False | 786 detach_head = False |
| 710 else: | 787 else: |
| 711 detach_head = True | 788 detach_head = True |
| 712 if options.verbose: | 789 if options.verbose: |
| 713 clone_cmd.append('--verbose') | 790 clone_cmd.append('--verbose') |
| 714 clone_cmd.extend([url, self.checkout_path]) | 791 clone_cmd.extend([url, self.checkout_path]) |
| 715 | 792 |
| 716 # If the parent directory does not exist, Git clone on Windows will not | 793 # If the parent directory does not exist, Git clone on Windows will not |
| 717 # create it, so we need to do it manually. | 794 # create it, so we need to do it manually. |
| 718 parent_dir = os.path.dirname(self.checkout_path) | 795 parent_dir = os.path.dirname(self.checkout_path) |
| 719 if not os.path.exists(parent_dir): | 796 if not os.path.exists(parent_dir): |
| 720 gclient_utils.safe_makedirs(parent_dir) | 797 gclient_utils.safe_makedirs(parent_dir) |
| 721 | 798 |
| 722 percent_re = re.compile('.* ([0-9]{1,2})% .*') | |
| 723 def _GitFilter(line): | |
| 724 # git uses an escape sequence to clear the line; elide it. | |
| 725 esc = line.find(unichr(033)) | |
| 726 if esc > -1: | |
| 727 line = line[:esc] | |
| 728 match = percent_re.match(line) | |
| 729 if not match or not int(match.group(1)) % 10: | |
| 730 print '%s' % line | |
| 731 | |
| 732 for _ in range(3): | 799 for _ in range(3): |
| 733 try: | 800 try: |
| 734 self._Run(clone_cmd, options, cwd=self._root_dir, filter_fn=_GitFilter, | 801 self._Run(clone_cmd, options, cwd=self._root_dir, git_filter=True) |
| 735 print_stdout=False) | |
| 736 break | 802 break |
| 737 except subprocess2.CalledProcessError, e: | 803 except subprocess2.CalledProcessError, e: |
| 738 # Too bad we don't have access to the actual output yet. | 804 # Too bad we don't have access to the actual output yet. |
| 739 # We should check for "transfer closed with NNN bytes remaining to | 805 # We should check for "transfer closed with NNN bytes remaining to |
| 740 # read". In the meantime, just make sure .git exists. | 806 # read". In the meantime, just make sure .git exists. |
| 741 if (e.returncode == 128 and | 807 if (e.returncode == 128 and |
| 742 os.path.exists(os.path.join(self.checkout_path, '.git'))): | 808 os.path.exists(os.path.join(self.checkout_path, '.git'))): |
| 743 print(str(e)) | 809 print(str(e)) |
| 744 print('Retrying...') | 810 print('Retrying...') |
| 745 continue | 811 continue |
| (...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 940 if options.verbose: | 1006 if options.verbose: |
| 941 fetch_cmd.append('--verbose') | 1007 fetch_cmd.append('--verbose') |
| 942 self._Run(fetch_cmd, options) | 1008 self._Run(fetch_cmd, options) |
| 943 break | 1009 break |
| 944 except subprocess2.CalledProcessError, e: | 1010 except subprocess2.CalledProcessError, e: |
| 945 print(str(e)) | 1011 print(str(e)) |
| 946 print('Retrying in %.1f seconds...' % backoff_time) | 1012 print('Retrying in %.1f seconds...' % backoff_time) |
| 947 time.sleep(backoff_time) | 1013 time.sleep(backoff_time) |
| 948 backoff_time *= 1.3 | 1014 backoff_time *= 1.3 |
| 949 | 1015 |
| 950 def _Run(self, args, options, **kwargs): | 1016 def _Run(self, args, _options, git_filter=False, **kwargs): |
| 951 kwargs.setdefault('cwd', self.checkout_path) | 1017 kwargs.setdefault('cwd', self.checkout_path) |
| 952 kwargs.setdefault('print_stdout', True) | |
| 953 kwargs.setdefault('nag_timer', self.nag_timer) | 1018 kwargs.setdefault('nag_timer', self.nag_timer) |
| 954 kwargs.setdefault('nag_max', self.nag_max) | 1019 kwargs.setdefault('nag_max', self.nag_max) |
| 1020 if git_filter: | |
| 1021 kwargs['filter_fn'] = GitFilter(kwargs['nag_timer'], | |
| 1022 kwargs.get('filter_fn')) | |
| 1023 kwargs.setdefault('print_stdout', False) | |
| 1024 else: | |
| 1025 kwargs.setdefault('print_stdout', True) | |
| 955 stdout = kwargs.get('stdout', sys.stdout) | 1026 stdout = kwargs.get('stdout', sys.stdout) |
| 956 stdout.write('\n________ running \'git %s\' in \'%s\'\n' % ( | 1027 stdout.write('\n________ running \'git %s\' in \'%s\'\n' % ( |
| 957 ' '.join(args), kwargs['cwd'])) | 1028 ' '.join(args), kwargs['cwd'])) |
| 958 gclient_utils.CheckCallAndFilter(['git'] + args, **kwargs) | 1029 gclient_utils.CheckCallAndFilter(['git'] + args, **kwargs) |
| 959 | 1030 |
| 960 | 1031 |
| 961 class SVNWrapper(SCMWrapper): | 1032 class SVNWrapper(SCMWrapper): |
| 962 """ Wrapper for SVN """ | 1033 """ Wrapper for SVN """ |
| 963 | 1034 |
| 964 @staticmethod | 1035 @staticmethod |
| (...skipping 375 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1340 new_command.append('--force') | 1411 new_command.append('--force') |
| 1341 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1412 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
| 1342 new_command.extend(('--accept', 'theirs-conflict')) | 1413 new_command.extend(('--accept', 'theirs-conflict')) |
| 1343 elif options.manually_grab_svn_rev: | 1414 elif options.manually_grab_svn_rev: |
| 1344 new_command.append('--force') | 1415 new_command.append('--force') |
| 1345 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1416 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
| 1346 new_command.extend(('--accept', 'postpone')) | 1417 new_command.extend(('--accept', 'postpone')) |
| 1347 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1418 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
| 1348 new_command.extend(('--accept', 'postpone')) | 1419 new_command.extend(('--accept', 'postpone')) |
| 1349 return new_command | 1420 return new_command |
| OLD | NEW |