| 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 from __future__ import print_function | 7 from __future__ import print_function |
| 8 | 8 |
| 9 import errno | 9 import errno |
| 10 import logging | 10 import logging |
| (...skipping 19 matching lines...) Expand all Loading... |
| 30 os.path.dirname(os.path.abspath(__file__)), 'gsutil.py') | 30 os.path.dirname(os.path.abspath(__file__)), 'gsutil.py') |
| 31 | 31 |
| 32 | 32 |
| 33 class NoUsableRevError(gclient_utils.Error): | 33 class NoUsableRevError(gclient_utils.Error): |
| 34 """Raised if requested revision isn't found in checkout.""" | 34 """Raised if requested revision isn't found in checkout.""" |
| 35 | 35 |
| 36 | 36 |
| 37 class DiffFiltererWrapper(object): | 37 class DiffFiltererWrapper(object): |
| 38 """Simple base class which tracks which file is being diffed and | 38 """Simple base class which tracks which file is being diffed and |
| 39 replaces instances of its file name in the original and | 39 replaces instances of its file name in the original and |
| 40 working copy lines of the svn/git diff output.""" | 40 working copy lines of the git diff output.""" |
| 41 index_string = None | 41 index_string = None |
| 42 original_prefix = "--- " | 42 original_prefix = "--- " |
| 43 working_prefix = "+++ " | 43 working_prefix = "+++ " |
| 44 | 44 |
| 45 def __init__(self, relpath, print_func): | 45 def __init__(self, relpath, print_func): |
| 46 # Note that we always use '/' as the path separator to be | 46 # Note that we always use '/' as the path separator to be |
| 47 # consistent with svn's cygwin-style output on Windows | 47 # consistent with cygwin-style output on Windows |
| 48 self._relpath = relpath.replace("\\", "/") | 48 self._relpath = relpath.replace("\\", "/") |
| 49 self._current_file = None | 49 self._current_file = None |
| 50 self._print_func = print_func | 50 self._print_func = print_func |
| 51 | 51 |
| 52 def SetCurrentFile(self, current_file): | 52 def SetCurrentFile(self, current_file): |
| 53 self._current_file = current_file | 53 self._current_file = current_file |
| 54 | 54 |
| 55 @property | 55 @property |
| 56 def _replacement_file(self): | 56 def _replacement_file(self): |
| 57 return posixpath.join(self._relpath, self._current_file) | 57 return posixpath.join(self._relpath, self._current_file) |
| 58 | 58 |
| 59 def _Replace(self, line): | 59 def _Replace(self, line): |
| 60 return line.replace(self._current_file, self._replacement_file) | 60 return line.replace(self._current_file, self._replacement_file) |
| 61 | 61 |
| 62 def Filter(self, line): | 62 def Filter(self, line): |
| 63 if (line.startswith(self.index_string)): | 63 if (line.startswith(self.index_string)): |
| 64 self.SetCurrentFile(line[len(self.index_string):]) | 64 self.SetCurrentFile(line[len(self.index_string):]) |
| 65 line = self._Replace(line) | 65 line = self._Replace(line) |
| 66 else: | 66 else: |
| 67 if (line.startswith(self.original_prefix) or | 67 if (line.startswith(self.original_prefix) or |
| 68 line.startswith(self.working_prefix)): | 68 line.startswith(self.working_prefix)): |
| 69 line = self._Replace(line) | 69 line = self._Replace(line) |
| 70 self._print_func(line) | 70 self._print_func(line) |
| 71 | 71 |
| 72 | 72 |
| 73 class SvnDiffFilterer(DiffFiltererWrapper): | |
| 74 index_string = "Index: " | |
| 75 | |
| 76 | |
| 77 class GitDiffFilterer(DiffFiltererWrapper): | 73 class GitDiffFilterer(DiffFiltererWrapper): |
| 78 index_string = "diff --git " | 74 index_string = "diff --git " |
| 79 | 75 |
| 80 def SetCurrentFile(self, current_file): | 76 def SetCurrentFile(self, current_file): |
| 81 # Get filename by parsing "a/<filename> b/<filename>" | 77 # Get filename by parsing "a/<filename> b/<filename>" |
| 82 self._current_file = current_file[:(len(current_file)/2)][2:] | 78 self._current_file = current_file[:(len(current_file)/2)][2:] |
| 83 | 79 |
| 84 def _Replace(self, line): | 80 def _Replace(self, line): |
| 85 return re.sub("[a|b]/" + self._current_file, self._replacement_file, line) | 81 return re.sub("[a|b]/" + self._current_file, self._replacement_file, line) |
| 86 | 82 |
| 87 | 83 |
| 88 ### SCM abstraction layer | 84 ### SCM abstraction layer |
| 89 | 85 |
| 90 # Factory Method for SCM wrapper creation | 86 # Factory Method for SCM wrapper creation |
| 91 | 87 |
| 92 def GetScmName(url): | 88 def GetScmName(url): |
| 93 if url: | 89 if not url: |
| 94 url, _ = gclient_utils.SplitUrlRevision(url) | 90 return None |
| 95 if (url.startswith('git://') or url.startswith('ssh://') or | 91 url, _ = gclient_utils.SplitUrlRevision(url) |
| 96 url.startswith('git+http://') or url.startswith('git+https://') or | 92 if url.endswith('.git'): |
| 97 url.endswith('.git') or url.startswith('sso://') or | 93 return 'git' |
| 98 'googlesource' in url): | 94 protocol = url.split('://')[0] |
| 99 return 'git' | 95 if protocol in ( |
| 100 elif (url.startswith('http://') or url.startswith('https://') or | 96 'file', 'git', 'git+http', 'git+https', 'http', 'https', 'ssh', 'sso'): |
| 101 url.startswith('svn://') or url.startswith('svn+ssh://')): | 97 return 'git' |
| 102 return 'svn' | |
| 103 elif url.startswith('file://'): | |
| 104 if url.endswith('.git'): | |
| 105 return 'git' | |
| 106 return 'svn' | |
| 107 return None | 98 return None |
| 108 | 99 |
| 109 | 100 |
| 110 def CreateSCM(url, root_dir=None, relpath=None, out_fh=None, out_cb=None): | 101 def CreateSCM(url, root_dir=None, relpath=None, out_fh=None, out_cb=None): |
| 111 SCM_MAP = { | 102 SCM_MAP = { |
| 112 'svn' : SVNWrapper, | |
| 113 'git' : GitWrapper, | 103 'git' : GitWrapper, |
| 114 } | 104 } |
| 115 | 105 |
| 116 scm_name = GetScmName(url) | 106 scm_name = GetScmName(url) |
| 117 if not scm_name in SCM_MAP: | 107 if not scm_name in SCM_MAP: |
| 118 raise gclient_utils.Error('No SCM found for url %s' % url) | 108 raise gclient_utils.Error('No SCM found for url %s' % url) |
| 119 scm_class = SCM_MAP[scm_name] | 109 scm_class = SCM_MAP[scm_name] |
| 120 if not scm_class.BinaryExists(): | 110 if not scm_class.BinaryExists(): |
| 121 raise gclient_utils.Error('%s command not found' % scm_name) | 111 raise gclient_utils.Error('%s command not found' % scm_name) |
| 122 return scm_class(url, root_dir, relpath, out_fh, out_cb) | 112 return scm_class(url, root_dir, relpath, out_fh, out_cb) |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 185 if os.path.exists(os.path.join(self.checkout_path, '.git')): | 175 if os.path.exists(os.path.join(self.checkout_path, '.git')): |
| 186 actual_remote_url = self._get_first_remote_url(self.checkout_path) | 176 actual_remote_url = self._get_first_remote_url(self.checkout_path) |
| 187 | 177 |
| 188 mirror = self.GetCacheMirror() | 178 mirror = self.GetCacheMirror() |
| 189 # If the cache is used, obtain the actual remote URL from there. | 179 # If the cache is used, obtain the actual remote URL from there. |
| 190 if (mirror and mirror.exists() and | 180 if (mirror and mirror.exists() and |
| 191 mirror.mirror_path.replace('\\', '/') == | 181 mirror.mirror_path.replace('\\', '/') == |
| 192 actual_remote_url.replace('\\', '/')): | 182 actual_remote_url.replace('\\', '/')): |
| 193 actual_remote_url = self._get_first_remote_url(mirror.mirror_path) | 183 actual_remote_url = self._get_first_remote_url(mirror.mirror_path) |
| 194 return actual_remote_url | 184 return actual_remote_url |
| 195 | |
| 196 # Svn | |
| 197 if os.path.exists(os.path.join(self.checkout_path, '.svn')): | |
| 198 return scm.SVN.CaptureLocalInfo([], self.checkout_path)['URL'] | |
| 199 return None | 185 return None |
| 200 | 186 |
| 201 def DoesRemoteURLMatch(self, options): | 187 def DoesRemoteURLMatch(self, options): |
| 202 """Determine whether the remote URL of this checkout is the expected URL.""" | 188 """Determine whether the remote URL of this checkout is the expected URL.""" |
| 203 if not os.path.exists(self.checkout_path): | 189 if not os.path.exists(self.checkout_path): |
| 204 # A checkout which doesn't exist can't be broken. | 190 # A checkout which doesn't exist can't be broken. |
| 205 return True | 191 return True |
| 206 | 192 |
| 207 actual_remote_url = self.GetActualRemoteURL(options) | 193 actual_remote_url = self.GetActualRemoteURL(options) |
| 208 if actual_remote_url: | 194 if actual_remote_url: |
| 209 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/') | 195 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/') |
| 210 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/')) | 196 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/')) |
| 211 else: | 197 else: |
| 212 # This may occur if the self.checkout_path exists but does not contain a | 198 # This may occur if the self.checkout_path exists but does not contain a |
| 213 # valid git or svn checkout. | 199 # valid git checkout. |
| 214 return False | 200 return False |
| 215 | 201 |
| 216 def _DeleteOrMove(self, force): | 202 def _DeleteOrMove(self, force): |
| 217 """Delete the checkout directory or move it out of the way. | 203 """Delete the checkout directory or move it out of the way. |
| 218 | 204 |
| 219 Args: | 205 Args: |
| 220 force: bool; if True, delete the directory. Otherwise, just move it. | 206 force: bool; if True, delete the directory. Otherwise, just move it. |
| 221 """ | 207 """ |
| 222 if force and os.environ.get('CHROME_HEADLESS') == '1': | 208 if force and os.environ.get('CHROME_HEADLESS') == '1': |
| 223 self.Print('_____ Conflicting directory found in %s. Removing.' | 209 self.Print('_____ Conflicting directory found in %s. Removing.' |
| (...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 496 | 482 |
| 497 if return_early: | 483 if return_early: |
| 498 return self._Capture(['rev-parse', '--verify', 'HEAD']) | 484 return self._Capture(['rev-parse', '--verify', 'HEAD']) |
| 499 | 485 |
| 500 cur_branch = self._GetCurrentBranch() | 486 cur_branch = self._GetCurrentBranch() |
| 501 | 487 |
| 502 # Cases: | 488 # Cases: |
| 503 # 0) HEAD is detached. Probably from our initial clone. | 489 # 0) HEAD is detached. Probably from our initial clone. |
| 504 # - make sure HEAD is contained by a named ref, then update. | 490 # - make sure HEAD is contained by a named ref, then update. |
| 505 # Cases 1-4. HEAD is a branch. | 491 # Cases 1-4. HEAD is a branch. |
| 506 # 1) current branch is not tracking a remote branch (could be git-svn) | 492 # 1) current branch is not tracking a remote branch |
| 507 # - try to rebase onto the new hash or branch | 493 # - try to rebase onto the new hash or branch |
| 508 # 2) current branch is tracking a remote branch with local committed | 494 # 2) current branch is tracking a remote branch with local committed |
| 509 # changes, but the DEPS file switched to point to a hash | 495 # changes, but the DEPS file switched to point to a hash |
| 510 # - rebase those changes on top of the hash | 496 # - rebase those changes on top of the hash |
| 511 # 3) current branch is tracking a remote branch w/or w/out changes, and | 497 # 3) current branch is tracking a remote branch w/or w/out changes, and |
| 512 # no DEPS switch | 498 # no DEPS switch |
| 513 # - see if we can FF, if not, prompt the user for rebase, merge, or stop | 499 # - see if we can FF, if not, prompt the user for rebase, merge, or stop |
| 514 # 4) current branch is tracking a remote branch, but DEPS switches to a | 500 # 4) current branch is tracking a remote branch, but DEPS switches to a |
| 515 # different remote branch, and | 501 # different remote branch, and |
| 516 # a) current branch has no local changes, and --force: | 502 # a) current branch has no local changes, and --force: |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 566 self._Checkout( | 552 self._Checkout( |
| 567 options, | 553 options, |
| 568 revision, | 554 revision, |
| 569 force=(options.force and options.delete_unversioned_trees), | 555 force=(options.force and options.delete_unversioned_trees), |
| 570 quiet=True, | 556 quiet=True, |
| 571 ) | 557 ) |
| 572 if not printed_path: | 558 if not printed_path: |
| 573 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False) | 559 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False) |
| 574 elif current_type == 'hash': | 560 elif current_type == 'hash': |
| 575 # case 1 | 561 # case 1 |
| 576 if scm.GIT.IsGitSvn(self.checkout_path) and upstream_branch is not None: | 562 # Can't find a merge-base since we don't know our upstream. That makes |
| 577 # Our git-svn branch (upstream_branch) is our upstream | 563 # this command VERY likely to produce a rebase failure. For now we |
| 578 self._AttemptRebase(upstream_branch, files, options, | 564 # assume origin is our upstream since that's what the old behavior was. |
| 579 newbase=revision, printed_path=printed_path, | 565 upstream_branch = self.remote |
| 580 merge=options.merge) | 566 if options.revision or deps_revision: |
| 581 printed_path = True | 567 upstream_branch = revision |
| 582 else: | 568 self._AttemptRebase(upstream_branch, files, options, |
| 583 # Can't find a merge-base since we don't know our upstream. That makes | 569 printed_path=printed_path, merge=options.merge) |
| 584 # this command VERY likely to produce a rebase failure. For now we | 570 printed_path = True |
| 585 # assume origin is our upstream since that's what the old behavior was. | |
| 586 upstream_branch = self.remote | |
| 587 if options.revision or deps_revision: | |
| 588 upstream_branch = revision | |
| 589 self._AttemptRebase(upstream_branch, files, options, | |
| 590 printed_path=printed_path, merge=options.merge) | |
| 591 printed_path = True | |
| 592 elif rev_type == 'hash': | 571 elif rev_type == 'hash': |
| 593 # case 2 | 572 # case 2 |
| 594 self._AttemptRebase(upstream_branch, files, options, | 573 self._AttemptRebase(upstream_branch, files, options, |
| 595 newbase=revision, printed_path=printed_path, | 574 newbase=revision, printed_path=printed_path, |
| 596 merge=options.merge) | 575 merge=options.merge) |
| 597 printed_path = True | 576 printed_path = True |
| 598 elif remote_ref and ''.join(remote_ref) != upstream_branch: | 577 elif remote_ref and ''.join(remote_ref) != upstream_branch: |
| 599 # case 4 | 578 # case 4 |
| 600 new_base = ''.join(remote_ref) | 579 new_base = ''.join(remote_ref) |
| 601 if not printed_path: | 580 if not printed_path: |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 781 """Display status information.""" | 760 """Display status information.""" |
| 782 if not os.path.isdir(self.checkout_path): | 761 if not os.path.isdir(self.checkout_path): |
| 783 self.Print('________ couldn\'t run status in %s:\n' | 762 self.Print('________ couldn\'t run status in %s:\n' |
| 784 'The directory does not exist.' % self.checkout_path) | 763 'The directory does not exist.' % self.checkout_path) |
| 785 else: | 764 else: |
| 786 try: | 765 try: |
| 787 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])] | 766 merge_base = [self._Capture(['merge-base', 'HEAD', self.remote])] |
| 788 except subprocess2.CalledProcessError: | 767 except subprocess2.CalledProcessError: |
| 789 merge_base = [] | 768 merge_base = [] |
| 790 self._Run(['diff', '--name-status'] + merge_base, options, | 769 self._Run(['diff', '--name-status'] + merge_base, options, |
| 791 stdout=self.out_fh) | 770 stdout=self.out_fh, always=options.verbose) |
| 792 if file_list is not None: | 771 if file_list is not None: |
| 793 files = self._Capture(['diff', '--name-only'] + merge_base).split() | 772 files = self._Capture(['diff', '--name-only'] + merge_base).split() |
| 794 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) | 773 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) |
| 795 | 774 |
| 796 def GetUsableRev(self, rev, options): | 775 def GetUsableRev(self, rev, options): |
| 797 """Finds a useful revision for this repository. | 776 """Finds a useful revision for this repository.""" |
| 798 | |
| 799 If SCM is git-svn and the head revision is less than |rev|, git svn fetch | |
| 800 will be called on the source.""" | |
| 801 sha1 = None | 777 sha1 = None |
| 802 if not os.path.isdir(self.checkout_path): | 778 if not os.path.isdir(self.checkout_path): |
| 803 raise NoUsableRevError( | 779 raise NoUsableRevError( |
| 804 ( 'We could not find a valid hash for safesync_url response "%s".\n' | 780 ( 'We could not find a valid hash for safesync_url response "%s".\n' |
| 805 'Safesync URLs with a git checkout currently require the repo to\n' | 781 'Safesync URLs with a git checkout currently require the repo to\n' |
| 806 'be cloned without a safesync_url before adding the safesync_url.\n' | 782 'be cloned without a safesync_url before adding the safesync_url.\n' |
| 807 'For more info, see: ' | 783 'For more info, see: ' |
| 808 'http://code.google.com/p/chromium/wiki/UsingNewGit' | 784 'http://code.google.com/p/chromium/wiki/UsingNewGit' |
| 809 '#Initial_checkout' ) % rev) | 785 '#Initial_checkout' ) % rev) |
| 810 elif rev.isdigit() and len(rev) < 7: | 786 |
| 811 # Handles an SVN rev. As an optimization, only verify an SVN revision as | 787 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev): |
| 812 # [0-9]{1,6} for now to avoid making a network request. | 788 sha1 = rev |
| 813 if scm.GIT.IsGitSvn(cwd=self.checkout_path): | |
| 814 local_head = scm.GIT.GetGitSvnHeadRev(cwd=self.checkout_path) | |
| 815 if not local_head or local_head < int(rev): | |
| 816 try: | |
| 817 logging.debug('Looking for git-svn configuration optimizations.') | |
| 818 if scm.GIT.Capture(['config', '--get', 'svn-remote.svn.fetch'], | |
| 819 cwd=self.checkout_path): | |
| 820 self._Fetch(options) | |
| 821 except subprocess2.CalledProcessError: | |
| 822 logging.debug('git config --get svn-remote.svn.fetch failed, ' | |
| 823 'ignoring possible optimization.') | |
| 824 if options.verbose: | |
| 825 self.Print('Running git svn fetch. This might take a while.\n') | |
| 826 scm.GIT.Capture(['svn', 'fetch'], cwd=self.checkout_path) | |
| 827 try: | |
| 828 sha1 = scm.GIT.GetBlessedSha1ForSvnRev( | |
| 829 cwd=self.checkout_path, rev=rev) | |
| 830 except gclient_utils.Error, e: | |
| 831 sha1 = e.message | |
| 832 self.Print('Warning: Could not find a git revision with accurate\n' | |
| 833 '.DEPS.git that maps to SVN revision %s. Sync-ing to\n' | |
| 834 'the closest sane git revision, which is:\n' | |
| 835 ' %s\n' % (rev, e.message)) | |
| 836 if not sha1: | |
| 837 raise NoUsableRevError( | |
| 838 ( 'It appears that either your git-svn remote is incorrectly\n' | |
| 839 'configured or the revision in your safesync_url is\n' | |
| 840 'higher than git-svn remote\'s HEAD as we couldn\'t find a\n' | |
| 841 'corresponding git hash for SVN rev %s.' ) % rev) | |
| 842 else: | 789 else: |
| 790 # May exist in origin, but we don't have it yet, so fetch and look |
| 791 # again. |
| 792 self._Fetch(options) |
| 843 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev): | 793 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev): |
| 844 sha1 = rev | 794 sha1 = rev |
| 845 else: | |
| 846 # May exist in origin, but we don't have it yet, so fetch and look | |
| 847 # again. | |
| 848 self._Fetch(options) | |
| 849 if scm.GIT.IsValidRevision(cwd=self.checkout_path, rev=rev): | |
| 850 sha1 = rev | |
| 851 | 795 |
| 852 if not sha1: | 796 if not sha1: |
| 853 raise NoUsableRevError( | 797 raise NoUsableRevError( |
| 854 ( 'We could not find a valid hash for safesync_url response "%s".\n' | 798 ('We could not find a valid hash for safesync_url response "%s".\n' |
| 855 'Safesync URLs with a git checkout currently require a git-svn\n' | 799 'Please ensure that your safesync_url provides git sha1 hashes.\n' |
| 856 'remote or a safesync_url that provides git sha1s. Please add a\n' | 800 'For more info, see:\n' |
| 857 'git-svn remote or change your safesync_url. For more info, see:\n' | 801 'http://code.google.com/p/chromium/wiki/UsingNewGit#Initial_checkout' |
| 858 'http://code.google.com/p/chromium/wiki/UsingNewGit' | 802 ) % rev) |
| 859 '#Initial_checkout' ) % rev) | |
| 860 | 803 |
| 861 return sha1 | 804 return sha1 |
| 862 | 805 |
| 863 def FullUrlForRelativeUrl(self, url): | 806 def FullUrlForRelativeUrl(self, url): |
| 864 # Strip from last '/' | 807 # Strip from last '/' |
| 865 # Equivalent to unix basename | 808 # Equivalent to unix basename |
| 866 base_url = self.url | 809 base_url = self.url |
| 867 return base_url[:base_url.rfind('/')] + url | 810 return base_url[:base_url.rfind('/')] + url |
| 868 | 811 |
| 869 def GetGitBackupDirPath(self): | 812 def GetGitBackupDirPath(self): |
| (...skipping 362 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1232 kwargs.setdefault('cwd', self.checkout_path) | 1175 kwargs.setdefault('cwd', self.checkout_path) |
| 1233 kwargs.setdefault('stdout', self.out_fh) | 1176 kwargs.setdefault('stdout', self.out_fh) |
| 1234 kwargs['filter_fn'] = self.filter | 1177 kwargs['filter_fn'] = self.filter |
| 1235 kwargs.setdefault('print_stdout', False) | 1178 kwargs.setdefault('print_stdout', False) |
| 1236 env = scm.GIT.ApplyEnvVars(kwargs) | 1179 env = scm.GIT.ApplyEnvVars(kwargs) |
| 1237 cmd = ['git'] + args | 1180 cmd = ['git'] + args |
| 1238 if show_header: | 1181 if show_header: |
| 1239 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs) | 1182 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs) |
| 1240 else: | 1183 else: |
| 1241 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs) | 1184 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs) |
| 1242 | |
| 1243 | |
| 1244 class SVNWrapper(SCMWrapper): | |
| 1245 """ Wrapper for SVN """ | |
| 1246 name = 'svn' | |
| 1247 _PRINTED_DEPRECATION = False | |
| 1248 | |
| 1249 _MESSAGE = ( | |
| 1250 'Oh hai! You are using subversion. Chrome infra is eager to get rid of', | |
| 1251 'svn support so please switch to git.', | |
| 1252 'Tracking bug: http://crbug.com/475320', | |
| 1253 'If you are a project owner, you may request git migration assistance at: ', | |
| 1254 ' https://code.google.com/p/chromium/issues/entry?template=Infra-Git') | |
| 1255 | |
| 1256 def __init__(self, *args, **kwargs): | |
| 1257 super(SVNWrapper, self).__init__(*args, **kwargs) | |
| 1258 suppress_deprecated_notice = os.environ.get( | |
| 1259 'SUPPRESS_DEPRECATED_SVN_NOTICE', False) | |
| 1260 if not SVNWrapper._PRINTED_DEPRECATION and not suppress_deprecated_notice: | |
| 1261 SVNWrapper._PRINTED_DEPRECATION = True | |
| 1262 sys.stderr.write('\n'.join(self._MESSAGE) + '\n') | |
| 1263 | |
| 1264 @staticmethod | |
| 1265 def BinaryExists(): | |
| 1266 """Returns true if the command exists.""" | |
| 1267 try: | |
| 1268 result, version = scm.SVN.AssertVersion('1.4') | |
| 1269 if not result: | |
| 1270 raise gclient_utils.Error('SVN version is older than 1.4: %s' % version) | |
| 1271 return result | |
| 1272 except OSError: | |
| 1273 return False | |
| 1274 | |
| 1275 def GetCheckoutRoot(self): | |
| 1276 return scm.SVN.GetCheckoutRoot(self.checkout_path) | |
| 1277 | |
| 1278 def GetRevisionDate(self, revision): | |
| 1279 """Returns the given revision's date in ISO-8601 format (which contains the | |
| 1280 time zone).""" | |
| 1281 date = scm.SVN.Capture( | |
| 1282 ['propget', '--revprop', 'svn:date', '-r', revision], | |
| 1283 os.path.join(self.checkout_path, '.')) | |
| 1284 return date.strip() | |
| 1285 | |
| 1286 def cleanup(self, options, args, _file_list): | |
| 1287 """Cleanup working copy.""" | |
| 1288 self._Run(['cleanup'] + args, options) | |
| 1289 | |
| 1290 def diff(self, options, args, _file_list): | |
| 1291 # NOTE: This function does not currently modify file_list. | |
| 1292 if not os.path.isdir(self.checkout_path): | |
| 1293 raise gclient_utils.Error('Directory %s is not present.' % | |
| 1294 self.checkout_path) | |
| 1295 self._Run(['diff'] + args, options) | |
| 1296 | |
| 1297 def pack(self, _options, args, _file_list): | |
| 1298 """Generates a patch file which can be applied to the root of the | |
| 1299 repository.""" | |
| 1300 if not os.path.isdir(self.checkout_path): | |
| 1301 raise gclient_utils.Error('Directory %s is not present.' % | |
| 1302 self.checkout_path) | |
| 1303 gclient_utils.CheckCallAndFilter( | |
| 1304 ['svn', 'diff', '-x', '--ignore-eol-style'] + args, | |
| 1305 cwd=self.checkout_path, | |
| 1306 print_stdout=False, | |
| 1307 filter_fn=SvnDiffFilterer(self.relpath, print_func=self.Print).Filter) | |
| 1308 | |
| 1309 def update(self, options, args, file_list): | |
| 1310 """Runs svn to update or transparently checkout the working copy. | |
| 1311 | |
| 1312 All updated files will be appended to file_list. | |
| 1313 | |
| 1314 Raises: | |
| 1315 Error: if can't get URL for relative path. | |
| 1316 """ | |
| 1317 # Only update if hg is not controlling the directory. | |
| 1318 hg_path = os.path.join(self.checkout_path, '.hg') | |
| 1319 if os.path.exists(hg_path): | |
| 1320 self.Print('________ found .hg directory; skipping %s' % self.relpath) | |
| 1321 return | |
| 1322 | |
| 1323 if args: | |
| 1324 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args)) | |
| 1325 | |
| 1326 # revision is the revision to match. It is None if no revision is specified, | |
| 1327 # i.e. the 'deps ain't pinned'. | |
| 1328 url, revision = gclient_utils.SplitUrlRevision(self.url) | |
| 1329 # Keep the original unpinned url for reference in case the repo is switched. | |
| 1330 base_url = url | |
| 1331 managed = True | |
| 1332 if options.revision: | |
| 1333 # Override the revision number. | |
| 1334 revision = str(options.revision) | |
| 1335 if revision: | |
| 1336 if revision != 'unmanaged': | |
| 1337 forced_revision = True | |
| 1338 # Reconstruct the url. | |
| 1339 url = '%s@%s' % (url, revision) | |
| 1340 rev_str = ' at %s' % revision | |
| 1341 else: | |
| 1342 managed = False | |
| 1343 revision = None | |
| 1344 else: | |
| 1345 forced_revision = False | |
| 1346 rev_str = '' | |
| 1347 | |
| 1348 exists = os.path.exists(self.checkout_path) | |
| 1349 if exists and managed: | |
| 1350 # Git is only okay if it's a git-svn checkout of the right repo. | |
| 1351 if scm.GIT.IsGitSvn(self.checkout_path): | |
| 1352 remote_url = scm.GIT.Capture(['config', '--local', '--get', | |
| 1353 'svn-remote.svn.url'], | |
| 1354 cwd=self.checkout_path).rstrip() | |
| 1355 if remote_url.rstrip('/') == base_url.rstrip('/'): | |
| 1356 self.Print('\n_____ %s looks like a git-svn checkout. Skipping.' | |
| 1357 % self.relpath) | |
| 1358 return # TODO(borenet): Get the svn revision number? | |
| 1359 | |
| 1360 # Get the existing scm url and the revision number of the current checkout. | |
| 1361 if exists and managed: | |
| 1362 try: | |
| 1363 from_info = scm.SVN.CaptureLocalInfo( | |
| 1364 [], os.path.join(self.checkout_path, '.')) | |
| 1365 except (gclient_utils.Error, subprocess2.CalledProcessError): | |
| 1366 self._DeleteOrMove(options.force) | |
| 1367 exists = False | |
| 1368 | |
| 1369 BASE_URLS = { | |
| 1370 '/chrome/trunk/src': 'gs://chromium-svn-checkout/chrome/', | |
| 1371 '/blink/trunk': 'gs://chromium-svn-checkout/blink/', | |
| 1372 } | |
| 1373 WHITELISTED_ROOTS = [ | |
| 1374 'svn://svn.chromium.org', | |
| 1375 'svn://svn-mirror.golo.chromium.org', | |
| 1376 ] | |
| 1377 if not exists: | |
| 1378 try: | |
| 1379 # Split out the revision number since it's not useful for us. | |
| 1380 base_path = urlparse.urlparse(url).path.split('@')[0] | |
| 1381 # Check to see if we're on a whitelisted root. We do this because | |
| 1382 # only some svn servers have matching UUIDs. | |
| 1383 local_parsed = urlparse.urlparse(url) | |
| 1384 local_root = '%s://%s' % (local_parsed.scheme, local_parsed.netloc) | |
| 1385 if ('CHROME_HEADLESS' in os.environ | |
| 1386 and sys.platform == 'linux2' # TODO(hinoka): Enable for win/mac. | |
| 1387 and base_path in BASE_URLS | |
| 1388 and local_root in WHITELISTED_ROOTS): | |
| 1389 | |
| 1390 # Use a tarball for initial sync if we are on a bot. | |
| 1391 # Get an unauthenticated gsutil instance. | |
| 1392 gsutil = download_from_google_storage.Gsutil( | |
| 1393 GSUTIL_DEFAULT_PATH, boto_path=os.devnull) | |
| 1394 | |
| 1395 gs_path = BASE_URLS[base_path] | |
| 1396 _, out, _ = gsutil.check_call('ls', gs_path) | |
| 1397 # So that we can get the most recent revision. | |
| 1398 sorted_items = sorted(out.splitlines()) | |
| 1399 latest_checkout = sorted_items[-1] | |
| 1400 | |
| 1401 tempdir = tempfile.mkdtemp() | |
| 1402 self.Print('Downloading %s...' % latest_checkout) | |
| 1403 code, out, err = gsutil.check_call('cp', latest_checkout, tempdir) | |
| 1404 if code: | |
| 1405 self.Print('%s\n%s' % (out, err)) | |
| 1406 raise Exception() | |
| 1407 filename = latest_checkout.split('/')[-1] | |
| 1408 tarball = os.path.join(tempdir, filename) | |
| 1409 self.Print('Unpacking into %s...' % self.checkout_path) | |
| 1410 gclient_utils.safe_makedirs(self.checkout_path) | |
| 1411 # TODO(hinoka): Use 7z for windows. | |
| 1412 cmd = ['tar', '--extract', '--ungzip', | |
| 1413 '--directory', self.checkout_path, | |
| 1414 '--file', tarball] | |
| 1415 gclient_utils.CheckCallAndFilter( | |
| 1416 cmd, stdout=sys.stdout, print_stdout=True) | |
| 1417 | |
| 1418 self.Print('Deleting temp file') | |
| 1419 gclient_utils.rmtree(tempdir) | |
| 1420 | |
| 1421 # Rewrite the repository root to match. | |
| 1422 tarball_url = scm.SVN.CaptureLocalInfo( | |
| 1423 ['.'], self.checkout_path)['Repository Root'] | |
| 1424 tarball_parsed = urlparse.urlparse(tarball_url) | |
| 1425 tarball_root = '%s://%s' % (tarball_parsed.scheme, | |
| 1426 tarball_parsed.netloc) | |
| 1427 | |
| 1428 if tarball_root != local_root: | |
| 1429 self.Print('Switching repository root to %s' % local_root) | |
| 1430 self._Run(['switch', '--relocate', tarball_root, | |
| 1431 local_root, self.checkout_path], | |
| 1432 options) | |
| 1433 except Exception as e: | |
| 1434 self.Print('We tried to get a source tarball but failed.') | |
| 1435 self.Print('Resuming normal operations.') | |
| 1436 self.Print(str(e)) | |
| 1437 | |
| 1438 gclient_utils.safe_makedirs(os.path.dirname(self.checkout_path)) | |
| 1439 # We need to checkout. | |
| 1440 command = ['checkout', url, self.checkout_path] | |
| 1441 command = self._AddAdditionalUpdateFlags(command, options, revision) | |
| 1442 self._RunAndGetFileList(command, options, file_list, self._root_dir) | |
| 1443 return self.Svnversion() | |
| 1444 | |
| 1445 if not managed: | |
| 1446 self.Print(('________ unmanaged solution; skipping %s' % self.relpath)) | |
| 1447 if os.path.exists(os.path.join(self.checkout_path, '.svn')): | |
| 1448 return self.Svnversion() | |
| 1449 return | |
| 1450 | |
| 1451 if 'URL' not in from_info: | |
| 1452 raise gclient_utils.Error( | |
| 1453 ('gclient is confused. Couldn\'t get the url for %s.\n' | |
| 1454 'Try using @unmanaged.\n%s') % ( | |
| 1455 self.checkout_path, from_info)) | |
| 1456 | |
| 1457 # Look for locked directories. | |
| 1458 dir_info = scm.SVN.CaptureStatus( | |
| 1459 None, os.path.join(self.checkout_path, '.')) | |
| 1460 if any(d[0][2] == 'L' for d in dir_info): | |
| 1461 try: | |
| 1462 self._Run(['cleanup', self.checkout_path], options) | |
| 1463 except subprocess2.CalledProcessError, e: | |
| 1464 # Get the status again, svn cleanup may have cleaned up at least | |
| 1465 # something. | |
| 1466 dir_info = scm.SVN.CaptureStatus( | |
| 1467 None, os.path.join(self.checkout_path, '.')) | |
| 1468 | |
| 1469 # Try to fix the failures by removing troublesome files. | |
| 1470 for d in dir_info: | |
| 1471 if d[0][2] == 'L': | |
| 1472 if d[0][0] == '!' and options.force: | |
| 1473 # We don't pass any files/directories to CaptureStatus and set | |
| 1474 # cwd=self.checkout_path, so we should get relative paths here. | |
| 1475 assert not os.path.isabs(d[1]) | |
| 1476 path_to_remove = os.path.normpath( | |
| 1477 os.path.join(self.checkout_path, d[1])) | |
| 1478 self.Print('Removing troublesome path %s' % path_to_remove) | |
| 1479 gclient_utils.rmtree(path_to_remove) | |
| 1480 else: | |
| 1481 self.Print( | |
| 1482 'Not removing troublesome path %s automatically.' % d[1]) | |
| 1483 if d[0][0] == '!': | |
| 1484 self.Print('You can pass --force to enable automatic removal.') | |
| 1485 raise e | |
| 1486 | |
| 1487 if from_info['URL'].rstrip('/') != base_url.rstrip('/'): | |
| 1488 # The repository url changed, need to switch. | |
| 1489 try: | |
| 1490 to_info = scm.SVN.CaptureRemoteInfo(url) | |
| 1491 except (gclient_utils.Error, subprocess2.CalledProcessError): | |
| 1492 # The url is invalid or the server is not accessible, it's safer to bail | |
| 1493 # out right now. | |
| 1494 raise gclient_utils.Error('This url is unreachable: %s' % url) | |
| 1495 can_switch = ((from_info['Repository Root'] != to_info['Repository Root']) | |
| 1496 and (from_info['UUID'] == to_info['UUID'])) | |
| 1497 if can_switch: | |
| 1498 self.Print('_____ relocating %s to a new checkout' % self.relpath) | |
| 1499 # We have different roots, so check if we can switch --relocate. | |
| 1500 # Subversion only permits this if the repository UUIDs match. | |
| 1501 # Perform the switch --relocate, then rewrite the from_url | |
| 1502 # to reflect where we "are now." (This is the same way that | |
| 1503 # Subversion itself handles the metadata when switch --relocate | |
| 1504 # is used.) This makes the checks below for whether we | |
| 1505 # can update to a revision or have to switch to a different | |
| 1506 # branch work as expected. | |
| 1507 # TODO(maruel): TEST ME ! | |
| 1508 command = ['switch', '--relocate', | |
| 1509 from_info['Repository Root'], | |
| 1510 to_info['Repository Root'], | |
| 1511 self.relpath] | |
| 1512 self._Run(command, options, cwd=self._root_dir) | |
| 1513 from_info['URL'] = from_info['URL'].replace( | |
| 1514 from_info['Repository Root'], | |
| 1515 to_info['Repository Root']) | |
| 1516 else: | |
| 1517 if not options.force and not options.reset: | |
| 1518 # Look for local modifications but ignore unversioned files. | |
| 1519 for status in scm.SVN.CaptureStatus(None, self.checkout_path): | |
| 1520 if status[0][0] != '?': | |
| 1521 raise gclient_utils.Error( | |
| 1522 ('Can\'t switch the checkout to %s; UUID don\'t match and ' | |
| 1523 'there is local changes in %s. Delete the directory and ' | |
| 1524 'try again.') % (url, self.checkout_path)) | |
| 1525 # Ok delete it. | |
| 1526 self.Print('_____ switching %s to a new checkout' % self.relpath) | |
| 1527 gclient_utils.rmtree(self.checkout_path) | |
| 1528 # We need to checkout. | |
| 1529 command = ['checkout', url, self.checkout_path] | |
| 1530 command = self._AddAdditionalUpdateFlags(command, options, revision) | |
| 1531 self._RunAndGetFileList(command, options, file_list, self._root_dir) | |
| 1532 return self.Svnversion() | |
| 1533 | |
| 1534 # If the provided url has a revision number that matches the revision | |
| 1535 # number of the existing directory, then we don't need to bother updating. | |
| 1536 if not options.force and str(from_info['Revision']) == revision: | |
| 1537 if options.verbose or not forced_revision: | |
| 1538 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False) | |
| 1539 else: | |
| 1540 command = ['update', self.checkout_path] | |
| 1541 command = self._AddAdditionalUpdateFlags(command, options, revision) | |
| 1542 self._RunAndGetFileList(command, options, file_list, self._root_dir) | |
| 1543 | |
| 1544 # If --reset and --delete_unversioned_trees are specified, remove any | |
| 1545 # untracked files and directories. | |
| 1546 if options.reset and options.delete_unversioned_trees: | |
| 1547 for status in scm.SVN.CaptureStatus(None, self.checkout_path): | |
| 1548 full_path = os.path.join(self.checkout_path, status[1]) | |
| 1549 if (status[0][0] == '?' | |
| 1550 and os.path.isdir(full_path) | |
| 1551 and not os.path.islink(full_path)): | |
| 1552 self.Print('_____ removing unversioned directory %s' % status[1]) | |
| 1553 gclient_utils.rmtree(full_path) | |
| 1554 return self.Svnversion() | |
| 1555 | |
| 1556 def updatesingle(self, options, args, file_list): | |
| 1557 filename = args.pop() | |
| 1558 if scm.SVN.AssertVersion("1.5")[0]: | |
| 1559 if not os.path.exists(os.path.join(self.checkout_path, '.svn')): | |
| 1560 # Create an empty checkout and then update the one file we want. Future | |
| 1561 # operations will only apply to the one file we checked out. | |
| 1562 command = ["checkout", "--depth", "empty", self.url, self.checkout_path] | |
| 1563 self._Run(command, options, cwd=self._root_dir) | |
| 1564 if os.path.exists(os.path.join(self.checkout_path, filename)): | |
| 1565 os.remove(os.path.join(self.checkout_path, filename)) | |
| 1566 command = ["update", filename] | |
| 1567 self._RunAndGetFileList(command, options, file_list) | |
| 1568 # After the initial checkout, we can use update as if it were any other | |
| 1569 # dep. | |
| 1570 self.update(options, args, file_list) | |
| 1571 else: | |
| 1572 # If the installed version of SVN doesn't support --depth, fallback to | |
| 1573 # just exporting the file. This has the downside that revision | |
| 1574 # information is not stored next to the file, so we will have to | |
| 1575 # re-export the file every time we sync. | |
| 1576 if not os.path.exists(self.checkout_path): | |
| 1577 gclient_utils.safe_makedirs(self.checkout_path) | |
| 1578 command = ["export", os.path.join(self.url, filename), | |
| 1579 os.path.join(self.checkout_path, filename)] | |
| 1580 command = self._AddAdditionalUpdateFlags(command, options, | |
| 1581 options.revision) | |
| 1582 self._Run(command, options, cwd=self._root_dir) | |
| 1583 | |
| 1584 def revert(self, options, _args, file_list): | |
| 1585 """Reverts local modifications. Subversion specific. | |
| 1586 | |
| 1587 All reverted files will be appended to file_list, even if Subversion | |
| 1588 doesn't know about them. | |
| 1589 """ | |
| 1590 if not os.path.isdir(self.checkout_path): | |
| 1591 if os.path.exists(self.checkout_path): | |
| 1592 gclient_utils.rmtree(self.checkout_path) | |
| 1593 # svn revert won't work if the directory doesn't exist. It needs to | |
| 1594 # checkout instead. | |
| 1595 self.Print('_____ %s is missing, synching instead' % self.relpath) | |
| 1596 # Don't reuse the args. | |
| 1597 return self.update(options, [], file_list) | |
| 1598 | |
| 1599 if not os.path.isdir(os.path.join(self.checkout_path, '.svn')): | |
| 1600 if os.path.isdir(os.path.join(self.checkout_path, '.git')): | |
| 1601 self.Print('________ found .git directory; skipping %s' % self.relpath) | |
| 1602 return | |
| 1603 if os.path.isdir(os.path.join(self.checkout_path, '.hg')): | |
| 1604 self.Print('________ found .hg directory; skipping %s' % self.relpath) | |
| 1605 return | |
| 1606 if not options.force: | |
| 1607 raise gclient_utils.Error('Invalid checkout path, aborting') | |
| 1608 self.Print( | |
| 1609 '\n_____ %s is not a valid svn checkout, synching instead' % | |
| 1610 self.relpath) | |
| 1611 gclient_utils.rmtree(self.checkout_path) | |
| 1612 # Don't reuse the args. | |
| 1613 return self.update(options, [], file_list) | |
| 1614 | |
| 1615 def printcb(file_status): | |
| 1616 if file_list is not None: | |
| 1617 file_list.append(file_status[1]) | |
| 1618 if logging.getLogger().isEnabledFor(logging.INFO): | |
| 1619 logging.info('%s%s' % (file_status[0], file_status[1])) | |
| 1620 else: | |
| 1621 self.Print(os.path.join(self.checkout_path, file_status[1])) | |
| 1622 scm.SVN.Revert(self.checkout_path, callback=printcb) | |
| 1623 | |
| 1624 # Revert() may delete the directory altogether. | |
| 1625 if not os.path.isdir(self.checkout_path): | |
| 1626 # Don't reuse the args. | |
| 1627 return self.update(options, [], file_list) | |
| 1628 | |
| 1629 try: | |
| 1630 # svn revert is so broken we don't even use it. Using | |
| 1631 # "svn up --revision BASE" achieve the same effect. | |
| 1632 # file_list will contain duplicates. | |
| 1633 self._RunAndGetFileList(['update', '--revision', 'BASE'], options, | |
| 1634 file_list) | |
| 1635 except OSError, e: | |
| 1636 # Maybe the directory disappeared meanwhile. Do not throw an exception. | |
| 1637 logging.error('Failed to update:\n%s' % str(e)) | |
| 1638 | |
| 1639 def revinfo(self, _options, _args, _file_list): | |
| 1640 """Display revision""" | |
| 1641 try: | |
| 1642 return scm.SVN.CaptureRevision(self.checkout_path) | |
| 1643 except (gclient_utils.Error, subprocess2.CalledProcessError): | |
| 1644 return None | |
| 1645 | |
| 1646 def runhooks(self, options, args, file_list): | |
| 1647 self.status(options, args, file_list) | |
| 1648 | |
| 1649 def status(self, options, args, file_list): | |
| 1650 """Display status information.""" | |
| 1651 command = ['status'] + args | |
| 1652 if not os.path.isdir(self.checkout_path): | |
| 1653 # svn status won't work if the directory doesn't exist. | |
| 1654 self.Print(('\n________ couldn\'t run \'%s\' in \'%s\':\n' | |
| 1655 'The directory does not exist.') % | |
| 1656 (' '.join(command), self.checkout_path)) | |
| 1657 # There's no file list to retrieve. | |
| 1658 else: | |
| 1659 self._RunAndGetFileList(command, options, file_list) | |
| 1660 | |
| 1661 def GetUsableRev(self, rev, _options): | |
| 1662 """Verifies the validity of the revision for this repository.""" | |
| 1663 if not scm.SVN.IsValidRevision(url='%s@%s' % (self.url, rev)): | |
| 1664 raise NoUsableRevError( | |
| 1665 ( '%s isn\'t a valid revision. Please check that your safesync_url is\n' | |
| 1666 'correct.') % rev) | |
| 1667 return rev | |
| 1668 | |
| 1669 def FullUrlForRelativeUrl(self, url): | |
| 1670 # Find the forth '/' and strip from there. A bit hackish. | |
| 1671 return '/'.join(self.url.split('/')[:4]) + url | |
| 1672 | |
| 1673 def _Run(self, args, options, **kwargs): | |
| 1674 """Runs a commands that goes to stdout.""" | |
| 1675 kwargs.setdefault('cwd', self.checkout_path) | |
| 1676 gclient_utils.CheckCallAndFilterAndHeader(['svn'] + args, | |
| 1677 always=options.verbose, **kwargs) | |
| 1678 | |
| 1679 def Svnversion(self): | |
| 1680 """Runs the lowest checked out revision in the current project.""" | |
| 1681 info = scm.SVN.CaptureLocalInfo([], os.path.join(self.checkout_path, '.')) | |
| 1682 return info['Revision'] | |
| 1683 | |
| 1684 def _RunAndGetFileList(self, args, options, file_list, cwd=None): | |
| 1685 """Runs a commands that goes to stdout and grabs the file listed.""" | |
| 1686 cwd = cwd or self.checkout_path | |
| 1687 scm.SVN.RunAndGetFileList( | |
| 1688 options.verbose, | |
| 1689 args + ['--ignore-externals'], | |
| 1690 cwd=cwd, | |
| 1691 file_list=file_list) | |
| 1692 | |
| 1693 @staticmethod | |
| 1694 def _AddAdditionalUpdateFlags(command, options, revision): | |
| 1695 """Add additional flags to command depending on what options are set. | |
| 1696 command should be a list of strings that represents an svn command. | |
| 1697 | |
| 1698 This method returns a new list to be used as a command.""" | |
| 1699 new_command = command[:] | |
| 1700 if revision: | |
| 1701 new_command.extend(['--revision', str(revision).strip()]) | |
| 1702 # We don't want interaction when jobs are used. | |
| 1703 if options.jobs > 1: | |
| 1704 new_command.append('--non-interactive') | |
| 1705 # --force was added to 'svn update' in svn 1.5. | |
| 1706 # --accept was added to 'svn update' in svn 1.6. | |
| 1707 if not scm.SVN.AssertVersion('1.5')[0]: | |
| 1708 return new_command | |
| 1709 | |
| 1710 # It's annoying to have it block in the middle of a sync, just sensible | |
| 1711 # defaults. | |
| 1712 if options.force: | |
| 1713 new_command.append('--force') | |
| 1714 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | |
| 1715 new_command.extend(('--accept', 'theirs-conflict')) | |
| 1716 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | |
| 1717 new_command.extend(('--accept', 'postpone')) | |
| 1718 return new_command | |
| OLD | NEW |