Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(193)

Side by Side Diff: gclient_scm.py

Issue 2393773003: Remove SVN support from gclient_utils and gclient_scm (Closed)
Patch Set: Rebase Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | gclient_utils.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
OLDNEW
« no previous file with comments | « no previous file | gclient_utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698