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, predicate=None): | |
| 159 """Create a new GitFilter. | |
| 160 | |
| 161 Args: | |
| 162 predicate (f(line)): An optional function which is invoked for every line. | |
| 163 The line will be skipped if the predicate is False. | |
| 164 """ | |
| 165 self.last_time = 0 | |
| 166 self.predicate = predicate | |
| 167 | |
| 168 def __call__(self, line): | |
| 169 # git uses an escape sequence to clear the line; elide it. | |
| 170 esc = line.find(unichr(033)) | |
| 171 if esc > -1: | |
| 172 line = line[:esc] | |
| 173 if self.predicate and not self.predicate(line): | |
| 174 return | |
| 175 now = time.time() | |
| 176 match = self.PERCENT_RE.match(line) | |
| 177 if not match: | |
| 178 self.last_time = 0 | |
| 179 if (now - self.last_time) >= 2: | |
|
szager1
2013/07/01 23:38:29
Maybe parameterize the suppression interval? Two
iannucci
2013/07/01 23:55:52
I was making it shorter explicitly FOR the bots. M
szager1
2013/07/02 00:08:05
That would be fine, or even something like 10; but
iannucci
2013/07/02 00:22:27
Ok, I set it to nag_timer / 2 (which should be 15
| |
| 180 self.last_time = now | |
| 181 print line | |
| 182 | |
| 183 | |
| 155 class GitWrapper(SCMWrapper): | 184 class GitWrapper(SCMWrapper): |
| 156 """Wrapper for Git""" | 185 """Wrapper for Git""" |
| 157 | 186 |
| 158 def __init__(self, url=None, root_dir=None, relpath=None): | 187 def __init__(self, url=None, root_dir=None, relpath=None): |
| 159 """Removes 'git+' fake prefix from git URL.""" | 188 """Removes 'git+' fake prefix from git URL.""" |
| 160 if url.startswith('git+http://') or url.startswith('git+https://'): | 189 if url.startswith('git+http://') or url.startswith('git+https://'): |
| 161 url = url[4:] | 190 url = url[4:] |
| 162 SCMWrapper.__init__(self, url, root_dir, relpath) | 191 SCMWrapper.__init__(self, url, root_dir, relpath) |
| 163 | 192 |
| 164 @staticmethod | 193 @staticmethod |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 290 rev_str = ' at %s' % revision | 319 rev_str = ' at %s' % revision |
| 291 files = [] | 320 files = [] |
| 292 | 321 |
| 293 printed_path = False | 322 printed_path = False |
| 294 verbose = [] | 323 verbose = [] |
| 295 if options.verbose: | 324 if options.verbose: |
| 296 print('\n_____ %s%s' % (self.relpath, rev_str)) | 325 print('\n_____ %s%s' % (self.relpath, rev_str)) |
| 297 verbose = ['--verbose'] | 326 verbose = ['--verbose'] |
| 298 printed_path = True | 327 printed_path = True |
| 299 | 328 |
| 329 url = self._CreateOrUpdateCache(url, options) | |
| 330 | |
| 300 if revision.startswith('refs/heads/'): | 331 if revision.startswith('refs/heads/'): |
| 301 rev_type = "branch" | 332 rev_type = "branch" |
| 302 elif revision.startswith('origin/'): | 333 elif revision.startswith('origin/'): |
| 303 # For compatability with old naming, translate 'origin' to 'refs/heads' | 334 # For compatability with old naming, translate 'origin' to 'refs/heads' |
| 304 revision = revision.replace('origin/', 'refs/heads/') | 335 revision = revision.replace('origin/', 'refs/heads/') |
| 305 rev_type = "branch" | 336 rev_type = "branch" |
| 306 else: | 337 else: |
| 307 # hash is also a tag, only make a distinction at checkout | 338 # hash is also a tag, only make a distinction at checkout |
| 308 rev_type = "hash" | 339 rev_type = "hash" |
| 309 | 340 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 330 if not os.path.exists(os.path.join(self.checkout_path, '.git')): | 361 if not os.path.exists(os.path.join(self.checkout_path, '.git')): |
| 331 raise gclient_utils.Error('\n____ %s%s\n' | 362 raise gclient_utils.Error('\n____ %s%s\n' |
| 332 '\tPath is not a git repo. No .git dir.\n' | 363 '\tPath is not a git repo. No .git dir.\n' |
| 333 '\tTo resolve:\n' | 364 '\tTo resolve:\n' |
| 334 '\t\trm -rf %s\n' | 365 '\t\trm -rf %s\n' |
| 335 '\tAnd run gclient sync again\n' | 366 '\tAnd run gclient sync again\n' |
| 336 % (self.relpath, rev_str, self.relpath)) | 367 % (self.relpath, rev_str, self.relpath)) |
| 337 | 368 |
| 338 # See if the url has changed (the unittests use git://foo for the url, let | 369 # See if the url has changed (the unittests use git://foo for the url, let |
| 339 # that through). | 370 # that through). |
| 340 current_url = self._Capture(['config', 'remote.origin.url']) | 371 current_url = self._Capture(['config', 'remote.origin.url']) |
|
szager1
2013/07/01 23:38:29
Maybe do something here to help people who are swi
iannucci
2013/07/01 23:55:52
I think that should all work as intended, since th
szager1
2013/07/02 00:08:05
Actually, what I meant was that when switching fro
iannucci
2013/07/02 00:22:27
Ah! Yeah we could do that with --reference, I thin
| |
| 341 # TODO(maruel): Delete url != 'git://foo' since it's just to make the | 372 # TODO(maruel): Delete url != 'git://foo' since it's just to make the |
| 342 # unit test pass. (and update the comment above) | 373 # unit test pass. (and update the comment above) |
| 343 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set. | 374 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set. |
| 344 # This allows devs to use experimental repos which have a different url | 375 # This allows devs to use experimental repos which have a different url |
| 345 # but whose branch(s) are the same as official repos. | 376 # but whose branch(s) are the same as official repos. |
| 346 if (current_url != url and | 377 if (current_url != url and |
| 347 url != 'git://foo' and | 378 url != 'git://foo' and |
| 348 subprocess2.capture( | 379 subprocess2.capture( |
| 349 ['git', 'config', 'remote.origin.gclient-auto-fix-url'], | 380 ['git', 'config', 'remote.origin.gclient-auto-fix-url'], |
| 350 cwd=self.checkout_path).strip() != 'False'): | 381 cwd=self.checkout_path).strip() != 'False'): |
| (...skipping 333 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 684 '#Initial_checkout' ) % rev) | 715 '#Initial_checkout' ) % rev) |
| 685 | 716 |
| 686 return sha1 | 717 return sha1 |
| 687 | 718 |
| 688 def FullUrlForRelativeUrl(self, url): | 719 def FullUrlForRelativeUrl(self, url): |
| 689 # Strip from last '/' | 720 # Strip from last '/' |
| 690 # Equivalent to unix basename | 721 # Equivalent to unix basename |
| 691 base_url = self.url | 722 base_url = self.url |
| 692 return base_url[:base_url.rfind('/')] + url | 723 return base_url[:base_url.rfind('/')] + url |
| 693 | 724 |
| 725 @staticmethod | |
| 726 def _CacheFolder(url, options): | |
| 727 """Transforms a url into the path to the corresponding git cache. | |
| 728 | |
| 729 Ex. (assuming cache_dir == '/cache') | |
| 730 IN: https://chromium.googlesource.com/chromium/src | |
| 731 OUT: /cache/chromium.googlesource.com-chromium-src.git | |
|
szager1
2013/07/01 23:38:29
Any reason not to mimic the full directory structu
iannucci
2013/07/01 23:55:52
Eh... I'm much more inclined to flatten it out, si
szager1
2013/07/02 00:08:05
I'd be satisfied with collision detection logic.
iannucci
2013/07/02 00:22:27
Done.
| |
| 732 """ | |
| 733 idx = url.find('://') | |
| 734 if idx != -1: | |
| 735 url = url[idx+3:] | |
| 736 url = url.replace('/', '-') | |
| 737 if not url.endswith('.git'): | |
| 738 url += '.git' | |
| 739 return os.path.join(options.cache_dir, url) | |
| 740 | |
| 741 def _CreateOrUpdateCache(self, url, options): | |
| 742 """Make a new git mirror or update existing mirror for |url|, and return the | |
| 743 mirror URI to clone from. | |
| 744 | |
| 745 If no cache-dir is specified, just return |url| unchanged. | |
| 746 """ | |
| 747 if not options.cache_dir: | |
| 748 return url | |
| 749 folder = self._CacheFolder(url, options) | |
| 750 v = ['-v'] if options.verbose else [] | |
| 751 filter_fn = lambda l: '[up to date]' not in l | |
| 752 with options.cache_lock: | |
| 753 lock = options.cache_locks[folder] | |
| 754 with lock: | |
| 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. | |
| 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') | |
|
szager1
2013/07/01 23:38:29
Hmm... not sure I agree with the use of --shared.
iannucci
2013/07/01 23:55:52
--local won't work correctly on windows, or cross-
szager1
2013/07/02 00:08:05
Fair enough!
| |
| 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): |
| 1017 if git_filter: | |
| 1018 kwargs['filter_fn'] = GitFilter(kwargs.get('filter_fn')) | |
| 1019 kwargs.setdefault('print_stdout', False) | |
| 951 kwargs.setdefault('cwd', self.checkout_path) | 1020 kwargs.setdefault('cwd', self.checkout_path) |
| 952 kwargs.setdefault('print_stdout', True) | 1021 kwargs.setdefault('print_stdout', True) |
| 953 kwargs.setdefault('nag_timer', self.nag_timer) | 1022 kwargs.setdefault('nag_timer', self.nag_timer) |
| 954 kwargs.setdefault('nag_max', self.nag_max) | 1023 kwargs.setdefault('nag_max', self.nag_max) |
| 955 stdout = kwargs.get('stdout', sys.stdout) | 1024 stdout = kwargs.get('stdout', sys.stdout) |
| 956 stdout.write('\n________ running \'git %s\' in \'%s\'\n' % ( | 1025 stdout.write('\n________ running \'git %s\' in \'%s\'\n' % ( |
| 957 ' '.join(args), kwargs['cwd'])) | 1026 ' '.join(args), kwargs['cwd'])) |
| 958 gclient_utils.CheckCallAndFilter(['git'] + args, **kwargs) | 1027 gclient_utils.CheckCallAndFilter(['git'] + args, **kwargs) |
| 959 | 1028 |
| 960 | 1029 |
| (...skipping 379 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1340 new_command.append('--force') | 1409 new_command.append('--force') |
| 1341 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1410 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
| 1342 new_command.extend(('--accept', 'theirs-conflict')) | 1411 new_command.extend(('--accept', 'theirs-conflict')) |
| 1343 elif options.manually_grab_svn_rev: | 1412 elif options.manually_grab_svn_rev: |
| 1344 new_command.append('--force') | 1413 new_command.append('--force') |
| 1345 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1414 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
| 1346 new_command.extend(('--accept', 'postpone')) | 1415 new_command.extend(('--accept', 'postpone')) |
| 1347 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1416 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
| 1348 new_command.extend(('--accept', 'postpone')) | 1417 new_command.extend(('--accept', 'postpone')) |
| 1349 return new_command | 1418 return new_command |
| OLD | NEW |