diff --git a/gclient_scm.py b/gclient_scm.py index 0389f4662..5ba1cd619 100644 --- a/gclient_scm.py +++ b/gclient_scm.py @@ -302,55 +302,66 @@ class GitWrapper(SCMWrapper): # hash is also a tag, only make a distinction at checkout rev_type = "hash" - if (not os.path.exists(self.checkout_path) or - (os.path.isdir(self.checkout_path) and - not os.path.exists(os.path.join(self.checkout_path, '.git')))): - self._Clone(revision, url, options) - self.UpdateSubmoduleConfig() - if file_list is not None: - files = self._Capture(['ls-files']).splitlines() - file_list.extend([os.path.join(self.checkout_path, f) for f in files]) - if not verbose: - # Make the output a little prettier. It's nice to have some whitespace - # between projects when cloning. - print('') - return self._Capture(['rev-parse', '--verify', 'HEAD']) - if not managed: self._UpdateBranchHeads(options, fetch=False) self.UpdateSubmoduleConfig() print ('________ unmanaged solution; skipping %s' % self.relpath) return self._Capture(['rev-parse', '--verify', 'HEAD']) - if not os.path.exists(os.path.join(self.checkout_path, '.git')): - raise gclient_utils.Error('\n____ %s%s\n' - '\tPath is not a git repo. No .git dir.\n' - '\tTo resolve:\n' - '\t\trm -rf %s\n' - '\tAnd run gclient sync again\n' - % (self.relpath, rev_str, self.relpath)) + needs_delete = False + exists = os.path.exists(self.checkout_path) + if exists and not os.path.exists(os.path.join(self.checkout_path, '.git')): + needs_delete = True # See if the url has changed (the unittests use git://foo for the url, let # that through). - current_url = self._Capture(['config', 'remote.%s.url' % self.remote]) return_early = False - # TODO(maruel): Delete url != 'git://foo' since it's just to make the - # unit test pass. (and update the comment above) - # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set. - # This allows devs to use experimental repos which have a different url - # but whose branch(s) are the same as official repos. - if (current_url != url and - url != 'git://foo' and - subprocess2.capture( - ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote], - cwd=self.checkout_path).strip() != 'False'): - print('_____ switching %s to a new upstream' % self.relpath) - # Make sure it's clean - self._CheckClean(rev_str) - # Switch over to the new upstream - self._Run(['remote', 'set-url', self.remote, url], options) - self._FetchAndReset(revision, file_list, options) - return_early = True + if exists and not needs_delete: + current_url = self._Capture(['config', 'remote.%s.url' % self.remote]) + # TODO(maruel): Delete url != 'git://foo' since it's just to make the + # unit test pass. (and update the comment above) + # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set. + # This allows devs to use experimental repos which have a different url + # but whose branch(s) are the same as official repos. + if (current_url != url and url != 'git://foo'): + auto_fix = 'True' + try: + auto_fix = self._Capture( + ['config', 'remote.%s.gclient-auto-fix-url' % self.remote], + cwd=self.checkout_path).strip() + except subprocess2.CalledProcessError: + pass + if auto_fix in ('1', 'true', 'True', 'yes', 'Yes', 'y', 'Y'): + print('_____ switching %s to a new upstream' % self.relpath) + # Make sure it's clean + self._CheckClean(rev_str) + # Switch over to the new upstream + self._Run(['remote', 'set-url', self.remote, url], options) + self._FetchAndReset(revision, file_list, options) + return_early = True + + if (needs_delete and + gclient_utils.enable_deletion_of_conflicting_checkouts()): + if options.force: + print('Conflicting directory found in %s. Removing.' + % self.checkout_path) + gclient_utils.rmtree(self.checkout_path) + else: + raise gclient_utils.Error('Conflicting directory found in %s. Please ' + 'delete it or run with --force.' + % self.checkout_path) + + if not os.path.exists(self.checkout_path): + self._Clone(revision, url, options) + self.UpdateSubmoduleConfig() + if file_list is not None: + files = self._Capture(['ls-files']).splitlines() + file_list.extend([os.path.join(self.checkout_path, f) for f in files]) + if not verbose: + # Make the output a little prettier. It's nice to have some whitespace + # between projects when cloning. + print('') + return self._Capture(['rev-parse', '--verify', 'HEAD']) # Need to do this in the normal path as well as in the post-remote-switch # path. @@ -625,7 +636,7 @@ class GitWrapper(SCMWrapper): elif rev.isdigit() and len(rev) < 7: # Handles an SVN rev. As an optimization, only verify an SVN revision as # [0-9]{1,6} for now to avoid making a network request. - if scm.GIT.IsGitSvn(cwd=self.checkout_path): + if scm.GIT.IsGitSvn(self.checkout_path): local_head = scm.GIT.GetGitSvnHeadRev(cwd=self.checkout_path) if not local_head or local_head < int(rev): try: @@ -1104,17 +1115,6 @@ class SVNWrapper(SCMWrapper): Raises: Error: if can't get URL for relative path. """ - # Only update if git or hg is not controlling the directory. - git_path = os.path.join(self.checkout_path, '.git') - if os.path.exists(git_path): - print('________ found .git directory; skipping %s' % self.relpath) - return - - hg_path = os.path.join(self.checkout_path, '.hg') - if os.path.exists(hg_path): - print('________ found .hg directory; skipping %s' % self.relpath) - return - if args: raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args)) @@ -1140,21 +1140,86 @@ class SVNWrapper(SCMWrapper): forced_revision = False rev_str = '' + if not managed: + print ('________ unmanaged solution; skipping %s' % self.relpath) + return self.Svnversion() + # Get the existing scm url and the revision number of the current checkout. exists = os.path.exists(self.checkout_path) - if exists and managed: + needs_delete = False + from_info = None + + if exists: + # If there's a git-svn checkout, verify that the svn-remote is correct. + if scm.GIT.IsGitSvn(self.checkout_path): + remote_url = scm.GIT.Capture(['config', '--local', '--get', + 'svn-remote.svn.url'], + cwd=self.checkout_path).rstrip() + if remote_url != base_url: + needs_delete = True + else: + print('\n_____ %s looks like a git-svn checkout. Skipping.' + % self.relpath) + return # TODO(borenet): Get the svn revision number? + try: from_info = scm.SVN.CaptureLocalInfo( [], os.path.join(self.checkout_path, '.')) except (gclient_utils.Error, subprocess2.CalledProcessError): - if options.reset and options.delete_unversioned_trees: - print 'Removing troublesome path %s' % self.checkout_path - gclient_utils.rmtree(self.checkout_path) - exists = False - else: - msg = ('Can\'t update/checkout %s if an unversioned directory is ' - 'present. Delete the directory and try again.') - raise gclient_utils.Error(msg % self.checkout_path) + if gclient_utils.enable_deletion_of_conflicting_checkouts(): + needs_delete = True + else: # TODO(borenet): Remove this once we can get rid of the guard. + if options.reset and options.delete_unversioned_trees: + print 'Removing troublesome path %s' % self.checkout_path + gclient_utils.rmtree(self.checkout_path) + exists = False + else: + msg = ('Can\'t update/checkout %s if an unversioned directory is ' + 'present. Delete the directory and try again.') + raise gclient_utils.Error(msg % self.checkout_path) + + # Switch the checkout if necessary. + if not needs_delete and from_info and from_info['URL'] != base_url: + # The repository url changed, need to switch. + try: + to_info = scm.SVN.CaptureRemoteInfo(url) + except (gclient_utils.Error, subprocess2.CalledProcessError): + # The url is invalid or the server is not accessible, it's safer to bail + # out right now. + raise gclient_utils.Error('This url is unreachable: %s' % url) + can_switch = ((from_info['Repository Root'] != to_info['Repository Root']) + and (from_info['UUID'] == to_info['UUID'])) + if can_switch: + print('\n_____ relocating %s to a new checkout' % self.relpath) + # We have different roots, so check if we can switch --relocate. + # Subversion only permits this if the repository UUIDs match. + # Perform the switch --relocate, then rewrite the from_url + # to reflect where we "are now." (This is the same way that + # Subversion itself handles the metadata when switch --relocate + # is used.) This makes the checks below for whether we + # can update to a revision or have to switch to a different + # branch work as expected. + # TODO(maruel): TEST ME ! + command = ['switch', '--relocate', + from_info['Repository Root'], + to_info['Repository Root'], + self.relpath] + self._Run(command, options, cwd=self._root_dir) + from_info['URL'] = from_info['URL'].replace( + from_info['Repository Root'], + to_info['Repository Root']) + else: + needs_delete = True + + if (needs_delete and + gclient_utils.enable_deletion_of_conflicting_checkouts()): + if options.force: + gclient_utils.rmtree(self.checkout_path) + exists = False + else: + raise gclient_utils.Error('Conflicting directory found in %s. Please ' + 'delete it or run with --force.' + % self.checkout_path) BASE_URLS = { '/chrome/trunk/src': 'gs://chromium-svn-checkout/chrome/', @@ -1232,10 +1297,6 @@ class SVNWrapper(SCMWrapper): self._RunAndGetFileList(command, options, file_list, self._root_dir) return self.Svnversion() - if not managed: - print ('________ unmanaged solution; skipping %s' % self.relpath) - return self.Svnversion() - if 'URL' not in from_info: raise gclient_utils.Error( ('gclient is confused. Couldn\'t get the url for %s.\n' @@ -1277,53 +1338,6 @@ class SVNWrapper(SCMWrapper): revision = str(from_info_live['Revision']) rev_str = ' at %s' % revision - if from_info['URL'] != base_url: - # The repository url changed, need to switch. - try: - to_info = scm.SVN.CaptureRemoteInfo(url) - except (gclient_utils.Error, subprocess2.CalledProcessError): - # The url is invalid or the server is not accessible, it's safer to bail - # out right now. - raise gclient_utils.Error('This url is unreachable: %s' % url) - can_switch = ((from_info['Repository Root'] != to_info['Repository Root']) - and (from_info['UUID'] == to_info['UUID'])) - if can_switch: - print('\n_____ relocating %s to a new checkout' % self.relpath) - # We have different roots, so check if we can switch --relocate. - # Subversion only permits this if the repository UUIDs match. - # Perform the switch --relocate, then rewrite the from_url - # to reflect where we "are now." (This is the same way that - # Subversion itself handles the metadata when switch --relocate - # is used.) This makes the checks below for whether we - # can update to a revision or have to switch to a different - # branch work as expected. - # TODO(maruel): TEST ME ! - command = ['switch', '--relocate', - from_info['Repository Root'], - to_info['Repository Root'], - self.relpath] - self._Run(command, options, cwd=self._root_dir) - from_info['URL'] = from_info['URL'].replace( - from_info['Repository Root'], - to_info['Repository Root']) - else: - if not options.force and not options.reset: - # Look for local modifications but ignore unversioned files. - for status in scm.SVN.CaptureStatus(None, self.checkout_path): - if status[0][0] != '?': - raise gclient_utils.Error( - ('Can\'t switch the checkout to %s; UUID don\'t match and ' - 'there is local changes in %s. Delete the directory and ' - 'try again.') % (url, self.checkout_path)) - # Ok delete it. - print('\n_____ switching %s to a new checkout' % self.relpath) - gclient_utils.rmtree(self.checkout_path) - # We need to checkout. - command = ['checkout', url, self.checkout_path] - command = self._AddAdditionalUpdateFlags(command, options, revision) - self._RunAndGetFileList(command, options, file_list, self._root_dir) - return self.Svnversion() - # If the provided url has a revision number that matches the revision # number of the existing directory, then we don't need to bother updating. if not options.force and str(from_info['Revision']) == revision: @@ -1393,9 +1407,6 @@ class SVNWrapper(SCMWrapper): if os.path.isdir(os.path.join(self.checkout_path, '.git')): print('________ found .git directory; skipping %s' % self.relpath) return - if os.path.isdir(os.path.join(self.checkout_path, '.hg')): - print('________ found .hg directory; skipping %s' % self.relpath) - return if not options.force: raise gclient_utils.Error('Invalid checkout path, aborting') print( diff --git a/gclient_utils.py b/gclient_utils.py index 47ea50286..11bd9fc84 100644 --- a/gclient_utils.py +++ b/gclient_utils.py @@ -10,6 +10,7 @@ import os import pipes import Queue import re +import socket import stat import subprocess import sys @@ -25,6 +26,20 @@ RETRY_MAX = 3 RETRY_INITIAL_SLEEP = 0.5 +def enable_deletion_of_conflicting_checkouts(): + """Determines whether to enable new checkout deletion behavior. + + Initially, enables the experimental functionality on a small set of + bots. + """ + # TODO(borenet): Remove this hack as soon as we've verified that it + # doesn't cause the bots to break. + if not os.environ.get('CHROME_HEADLESS'): + return False + return socket.gethostname() in ('vm859-m1', 'BUILD1-M1', + 'vm630-m1.golo.chromium.org') + + class Error(Exception): """gclient exception class.""" def __init__(self, msg, *args, **kwargs): diff --git a/scm.py b/scm.py index 30bb3d17e..1d1b98f31 100644 --- a/scm.py +++ b/scm.py @@ -166,12 +166,15 @@ class GIT(object): return GIT.ShortBranchName(GIT.GetBranchRef(cwd)) @staticmethod - def IsGitSvn(cwd): + def IsGitSvn(checkout_root): """Returns true if this repo looks like it's using git-svn.""" + # A git-svn checkout has a .git directory. + if not os.path.exists(os.path.join(checkout_root, '.git')): + return False # If you have any "svn-remote.*" config keys, we think you're using svn. try: GIT.Capture(['config', '--local', '--get-regexp', r'^svn-remote\.'], - cwd=cwd) + cwd=checkout_root) return True except subprocess2.CalledProcessError: return False @@ -408,7 +411,7 @@ class GIT(object): @staticmethod def GetSha1ForSvnRev(cwd, rev): """Returns a corresponding git sha1 for a SVN revision.""" - if not GIT.IsGitSvn(cwd=cwd): + if not GIT.IsGitSvn(cwd): return None try: output = GIT.Capture(['svn', 'find-rev', 'r' + str(rev)], cwd=cwd) diff --git a/tests/gclient_scm_test.py b/tests/gclient_scm_test.py index 741727e34..f97acd114 100755 --- a/tests/gclient_scm_test.py +++ b/tests/gclient_scm_test.py @@ -161,8 +161,6 @@ class SVNWrapperTestCase(BaseTestCase): def testRunCommandException(self): options = self.Options(verbose=False) - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, @@ -182,8 +180,6 @@ class SVNWrapperTestCase(BaseTestCase): gclient_scm.scm.SVN.Capture(['--version', '--quiet'], None ).AndReturn('1.5.1') # It'll to a checkout instead. - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) # Checkout. gclient_scm.os.path.exists(self.base_path).AndReturn(False) parent = gclient_scm.os.path.dirname(self.base_path) @@ -212,10 +208,7 @@ class SVNWrapperTestCase(BaseTestCase): gclient_scm.os.path.isdir(self.base_path).AndReturn(True) gclient_scm.os.path.isdir(join(self.base_path, '.svn')).AndReturn(False) gclient_scm.os.path.isdir(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.isdir(join(self.base_path, '.hg')).AndReturn(False) # Checkout. - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(False) parent = gclient_scm.os.path.dirname(self.base_path) gclient_scm.os.path.exists(parent).AndReturn(False) @@ -340,8 +333,6 @@ class SVNWrapperTestCase(BaseTestCase): file_info.url = self.url file_info.uuid = 'ABC' file_info.revision = 42 - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) # Checkout. gclient_scm.os.path.exists(self.base_path).AndReturn(False) parent = gclient_scm.os.path.dirname(self.base_path) @@ -373,8 +364,8 @@ class SVNWrapperTestCase(BaseTestCase): 'UUID': 'ABC', 'Revision': 42, } - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) + self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsGitSvn', True) + gclient_scm.scm.GIT.IsGitSvn(self.base_path).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(True) # Checkout or update. @@ -419,8 +410,8 @@ class SVNWrapperTestCase(BaseTestCase): 'UUID': 'ABC', 'Revision': 42, } - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) + self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsGitSvn', True) + gclient_scm.scm.GIT.IsGitSvn(self.base_path).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(True) # Checkout or update. @@ -455,8 +446,8 @@ class SVNWrapperTestCase(BaseTestCase): 'UUID': 'ABC', 'Revision': 42, } - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) + self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsGitSvn', True) + gclient_scm.scm.GIT.IsGitSvn(self.base_path).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(True) # Checkout or update. @@ -521,8 +512,8 @@ class SVNWrapperTestCase(BaseTestCase): file_list=files_list) # Now we fall back on scm.update(). - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) + self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsGitSvn', True) + gclient_scm.scm.GIT.IsGitSvn(self.base_path).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(True) gclient_scm.scm.SVN._CaptureInfo([], dotted_path).AndReturn(file_info) gclient_scm.scm.SVN._CaptureInfo([file_info['URL']], None @@ -591,8 +582,8 @@ class SVNWrapperTestCase(BaseTestCase): file_list=files_list) # Now we fall back on scm.update(). - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) + self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsGitSvn', True) + gclient_scm.scm.GIT.IsGitSvn(self.base_path).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(True) gclient_scm.scm.SVN._CaptureInfo( [], join(self.base_path, ".")).AndReturn(file_info) @@ -627,8 +618,8 @@ class SVNWrapperTestCase(BaseTestCase): # Now we fall back on scm.update(). files_list = self.mox.CreateMockAnything() - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) + self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsGitSvn', True) + gclient_scm.scm.GIT.IsGitSvn(self.base_path).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(True) gclient_scm.scm.SVN._CaptureInfo( [], join(self.base_path, '.')).AndReturn(file_info) @@ -644,31 +635,23 @@ class SVNWrapperTestCase(BaseTestCase): scm.updatesingle(options, ['DEPS'], files_list) self.checkstdout('\n_____ %s at 42\n' % self.relpath) - def testUpdateGit(self): + def testUpdateGitSvn(self): options = self.Options(verbose=True) - file_path = gclient_scm.os.path.join(self.root_dir, self.relpath, '.git') - gclient_scm.os.path.exists(file_path).AndReturn(True) - - self.mox.ReplayAll() - scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, - relpath=self.relpath) - file_list = [] - scm.update(options, self.args, file_list) - self.checkstdout( - ('________ found .git directory; skipping %s\n' % self.relpath)) - - def testUpdateHg(self): - options = self.Options(verbose=True) - gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) - gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(True) - + gclient_scm.os.path.exists(self.base_path).AndReturn(True) + self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsGitSvn', True) + gclient_scm.scm.GIT.IsGitSvn(self.base_path).AndReturn(True) + self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'Capture', True) + gclient_scm.scm.GIT.Capture(['config', '--local', '--get', + 'svn-remote.svn.url'], + cwd=self.base_path).AndReturn(self.url) self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, relpath=self.relpath) file_list = [] - scm.update(options, self.args, file_list) + scm.update(options, [], file_list) self.checkstdout( - ('________ found .hg directory; skipping %s\n' % self.relpath)) + ('\n_____ %s looks like a git-svn checkout. Skipping.\n' % self.relpath) + ) def testGetUsableRevSVN(self): # pylint: disable=E1101 @@ -1162,7 +1145,7 @@ class ManagedGitWrapperTestCaseMox(BaseTestCase): ).AndReturn(True) self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsGitSvn', True) - gclient_scm.scm.GIT.IsGitSvn(cwd=self.base_path).MultipleTimes( + gclient_scm.scm.GIT.IsGitSvn(self.base_path).MultipleTimes( ).AndReturn(False) gclient_scm.scm.os.path.isdir(self.base_path).AndReturn(True) @@ -1211,7 +1194,7 @@ class ManagedGitWrapperTestCaseMox(BaseTestCase): gclient_scm.scm.GIT.Capture(['fetch', 'origin'], cwd=self.base_path) self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsGitSvn', True) - gclient_scm.scm.GIT.IsGitSvn(cwd=self.base_path).MultipleTimes( + gclient_scm.scm.GIT.IsGitSvn(self.base_path).MultipleTimes( ).AndReturn(True) self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsValidRevision', True) diff --git a/tests/gclient_smoketest.py b/tests/gclient_smoketest.py index 427699816..b9766855a 100755 --- a/tests/gclient_smoketest.py +++ b/tests/gclient_smoketest.py @@ -109,9 +109,11 @@ class GClientSmokeBase(FakeReposTestBase): not re.match( r'_____ [^ ]+ : Attempting rebase onto [0-9a-f]+...', line) and - not re.match(r'_____ [^ ]+ at [^ ]+', line)): - # The two regexp above are a bit too broad, they are necessary only - # for git checkouts. + not re.match(r'_____ [^ ]+ at [^ ]+', line) and not + re.match( + r'_____ (.*) looks like a git-svn checkout. Skipping.', + line)): + # The regexp above are a bit too broad. self.fail(line) else: results.append([[match.group(1), match.group(2), match.group(3)]]) @@ -776,7 +778,59 @@ class GClientSmokeSVN(GClientSmokeBase): # Cripple src/third_party/foo and make sure gclient still succeeds. gclient_utils.rmtree(join(third_party, 'foo', '.svn')) - self.assertEquals(0, self.gclient(cmd)[-1]) + self.assertEquals(0, self.gclient(cmd + ['--force'])[-1]) + + def testSkipGitSvn(self): + # Check that gclient skips git-svn checkouts. + if not self.enabled: + return + + # Create the .gclient file. + svn_url = self.svn_base + 'trunk/src' + self.gclient(['config', svn_url], cwd=self.root_dir) + + # Create a git-svn checkout. + # Use check_output to hide the output from the subprocess. + subprocess2.check_output(['git', 'svn', 'clone', svn_url], + cwd=self.root_dir) + + # Ensure that gclient skips the git-svn checkout. + stdout, stderr, rc = self.gclient(['sync', '--jobs', '1']) + self.assertEquals(rc, 0) + self.assertFalse(stderr) + self.assertTrue('_____ src looks like a git-svn checkout. Skipping.' + in stdout) + self.checkBlock(stdout, [ + ['running', self.root_dir], + ['running', os.path.join(self.root_dir, 'src', 'file', 'other')], + ['running', self.root_dir], + ['running', self.root_dir], + ['running', self.root_dir], + ['running', self.root_dir], + ['running', self.root_dir], + ]) + + # But, we still need the DEPS to be checked out... + foo_dir = os.path.join(self.root_dir, 'src', 'third_party', 'foo') + foo_rev = subprocess2.check_output(['svnversion', foo_dir]).strip() + self.assertEquals(foo_rev, '1') + + other_dir = os.path.join(self.root_dir, 'src', 'other') + other_rev = subprocess2.check_output(['svnversion', other_dir]).strip() + self.assertEquals(other_rev, '2') + + # Verify that the DEPS are NOT skipped on a second update. + stdout, stderr, rc = self.gclient(['sync', '--jobs', '1']) + self.assertFalse(stderr) + self.assertTrue('_____ src looks like a git-svn checkout. Skipping.' + in stdout) + self.assertFalse( + '_____ src/other looks like a git-svn checkout. Skipping.' in stdout, + 'Non git-svn checkout is incorrectly skipped.') + self.assertFalse( + '_____ src/third_party/foo looks like a git-svn checkout. Skipping.' + in stdout, + 'Non git-svn checkout is incorrectly skipped.') class GClientSmokeSVNTransitive(GClientSmokeBase): @@ -1062,7 +1116,7 @@ class GClientSmokeGIT(GClientSmokeBase): self.assertTree(tree) # Pre-DEPS hooks run when syncing with --nohooks. - self.gclient(['sync', '--deps', 'mac', '--nohooks', + self.gclient(['sync', '--deps', 'mac', '--nohooks', '--force', '--revision', 'src@' + self.githash('repo_5', 2)]) tree = self.mangle_git_tree(('repo_5@2', 'src'), ('repo_1@2', 'src/repo1'), @@ -1074,7 +1128,7 @@ class GClientSmokeGIT(GClientSmokeBase): os.remove(join(self.root_dir, 'src', 'git_pre_deps_hooked')) # Pre-DEPS hooks don't run with --noprehooks - self.gclient(['sync', '--deps', 'mac', '--noprehooks', + self.gclient(['sync', '--deps', 'mac', '--noprehooks', '--force', '--revision', 'src@' + self.githash('repo_5', 2)]) tree = self.mangle_git_tree(('repo_5@2', 'src'), ('repo_1@2', 'src/repo1'), @@ -1396,6 +1450,78 @@ class GClientSmokeBoth(GClientSmokeBase): self.assertEquals(sorted(entries), sorted(expected)) + if gclient_utils.enable_deletion_of_conflicting_checkouts(): + def testDeleteConflictingCheckout(self): + if not self.enabled: + return + + # Create an initial svn checkout. + self.gclient(['config', '--spec', + 'solutions=[' + '{"name": "src",' + ' "url": "' + self.svn_base + 'trunk/src"},' + ']' + ]) + results = self.gclient(['sync', '--deps', 'mac']) + self.assertEqual(results[2], 0, 'Sync failed!') + + # Verify that we have the expected svn checkout. + results = self.gclient(['revinfo', '--deps', 'mac']) + actual = results[0].splitlines() + expected = [ + 'src: %strunk/src' % self.svn_base, + 'src/file/other: File("%strunk/other/DEPS")' % self.svn_base, + 'src/other: %strunk/other' % self.svn_base, + 'src/third_party/foo: %strunk/third_party/foo@1' % self.svn_base, + ] + self.assertEquals(actual, expected) + + # Change the desired checkout to git. + self.gclient(['config', '--spec', + 'solutions=[' + '{"name": "src",' + ' "url": "' + self.git_base + 'repo_1"},' + ']' + ]) + + # Verify that the sync succeeds with --force. + results = self.gclient(['sync', '--deps', 'mac', '--force']) + self.assertEqual(results[2], 0, 'Sync failed!') + + # Verify that we got the desired git checkout. + results = self.gclient(['revinfo', '--deps', 'mac']) + actual = results[0].splitlines() + expected = [ + 'src: %srepo_1' % self.git_base, + 'src/repo2: %srepo_2@%s' % (self.git_base, + self.githash('repo_2', 1)[:7]), + 'src/repo2/repo_renamed: %srepo_3' % self.git_base, + ] + self.assertEquals(actual, expected) + + # Change the desired checkout back to svn. + self.gclient(['config', '--spec', + 'solutions=[' + '{"name": "src",' + ' "url": "' + self.svn_base + 'trunk/src"},' + ']' + ]) + + # Verify that the sync succeeds. + results = self.gclient(['sync', '--deps', 'mac', '--force']) + self.assertEqual(results[2], 0, 'Sync failed!') + + # Verify that we have the expected svn checkout. + results = self.gclient(['revinfo', '--deps', 'mac']) + actual = results[0].splitlines() + expected = [ + 'src: %strunk/src' % self.svn_base, + 'src/file/other: File("%strunk/other/DEPS")' % self.svn_base, + 'src/other: %strunk/other' % self.svn_base, + 'src/third_party/foo: %strunk/third_party/foo@1' % self.svn_base, + ] + self.assertEquals(actual, expected) + class GClientSmokeFromCheckout(GClientSmokeBase): # WebKit abuses this. It has a .gclient and a DEPS from a checkout. diff --git a/tests/gclient_utils_test.py b/tests/gclient_utils_test.py index b0b743631..6be9e1f5f 100755 --- a/tests/gclient_utils_test.py +++ b/tests/gclient_utils_test.py @@ -29,7 +29,9 @@ class GclientUtilsUnittest(GclientUtilBase): def testMembersChanged(self): members = [ 'Annotated', 'AutoFlush', 'CheckCallAndFilter', 'CommandToStr', - 'CheckCallAndFilterAndHeader', 'Error', 'ExecutionQueue', 'FileRead', + 'CheckCallAndFilterAndHeader', + 'enable_deletion_of_conflicting_checkouts', # TODO(borenet): Remove! + 'Error', 'ExecutionQueue', 'FileRead', 'FileWrite', 'FindFileUpwards', 'FindGclientRoot', 'GetGClientRootAndEntries', 'GetEditor', 'GetExeSuffix', 'GetMacWinOrLinux', 'GitFilter', 'IsDateRevision', 'MakeDateRevision', @@ -39,7 +41,9 @@ class GclientUtilsUnittest(GclientUtilBase): 'GCLIENT_CHILDREN_LOCK', 'GClientChildren', 'SplitUrlRevision', 'SyntaxErrorToError', 'UpgradeToHttps', 'Wrapper', 'WorkItem', 'codecs', 'lockedmethod', 'logging', 'os', 'pipes', 'Queue', 're', - 'rmtree', 'safe_makedirs', 'safe_rename', 'stat', 'subprocess', + 'rmtree', 'safe_makedirs', 'safe_rename', + 'socket', # TODO(borenet): Remove! + 'stat', 'subprocess', 'subprocess2', 'sys', 'tempfile', 'threading', 'time', 'urlparse', ] diff --git a/tests/scm_unittest.py b/tests/scm_unittest.py index ff03c03e1..2a1de5331 100755 --- a/tests/scm_unittest.py +++ b/tests/scm_unittest.py @@ -169,6 +169,18 @@ class RealGitSvnTest(fake_repos.FakeReposTestBase): self._capture(['reset', '--hard', 'HEAD^']) self.assertEquals(scm.GIT.GetGitSvnHeadRev(cwd=self.clone_dir), 1) + def testIsGitSvn(self): + if not self.enabled: + return + # Git-svn + self.assertTrue(scm.GIT.IsGitSvn(self.clone_dir)) + # Pure git + git_dir = scm.os.path.join(self.FAKE_REPOS.git_root, 'repo_1') + self.assertFalse(scm.GIT.IsGitSvn(git_dir)) + # Pure svn + svn_dir = scm.os.path.join(self.FAKE_REPOS.svn_checkout, 'trunk') + self.assertFalse(scm.GIT.IsGitSvn(svn_dir)) + def testParseGitSvnSha1(self): test_sha1 = 'a5c63ce8671922e5c59c0dea49ef4f9d4a3020c9' expected_output = test_sha1 + '\n'