diff --git a/gclient_scm.py b/gclient_scm.py index af4665d60..8a3410a88 100644 --- a/gclient_scm.py +++ b/gclient_scm.py @@ -473,8 +473,6 @@ class GitWrapper(SCMWrapper): self._CheckMinVersion("1.6.6") - # If a dependency is not pinned, track the default remote branch. - default_rev = 'refs/remotes/%s/master' % self.remote url, deps_revision = gclient_utils.SplitUrlRevision(self.url) revision = deps_revision managed = True @@ -487,7 +485,9 @@ class GitWrapper(SCMWrapper): revision = deps_revision managed = False if not revision: - revision = default_rev + # If a dependency is not pinned, track the default remote branch. + revision = scm.GIT.GetRemoteHeadRef(self.checkout_path, self.url, + self.remote) if managed: self._DisableHooks() diff --git a/scm.py b/scm.py index 42f566bdd..812454b83 100644 --- a/scm.py +++ b/scm.py @@ -111,7 +111,7 @@ class GIT(object): return env @staticmethod - def Capture(args, cwd, strip_out=True, **kwargs): + def Capture(args, cwd=None, strip_out=True, **kwargs): env = GIT.ApplyEnvVars(kwargs) output = subprocess2.check_output( ['git'] + args, cwd=cwd, stderr=subprocess2.PIPE, env=env, **kwargs) @@ -193,6 +193,31 @@ class GIT(object): except subprocess2.CalledProcessError: return None + @staticmethod + def GetRemoteHeadRef(cwd, url, remote): + """Returns the full default remote branch reference, e.g. + 'refs/remotes/origin/main'.""" + if os.path.exists(cwd): + try: + # Try using local git copy first + ref = 'refs/remotes/%s/HEAD' % remote + return GIT.Capture(['symbolic-ref', ref], cwd=cwd) + except subprocess2.CalledProcessError: + pass + + try: + # Fetch information from git server + resp = GIT.Capture(['ls-remote', '--symref', url, 'HEAD']) + regex = r'^ref: (.*)\tHEAD$' + for line in resp.split('\n'): + m = re.match(regex, line) + if m: + return ''.join(GIT.RefToRemoteRef(m.group(1), remote)) + except subprocess2.CalledProcessError: + pass + # Return default branch + return 'refs/remotes/%s/master' % remote + @staticmethod def GetBranch(cwd): """Returns the short branch name, e.g. 'main'.""" diff --git a/tests/gclient_scm_test.py b/tests/gclient_scm_test.py index d5fafed9d..6fa3bd52e 100755 --- a/tests/gclient_scm_test.py +++ b/tests/gclient_scm_test.py @@ -196,6 +196,11 @@ from :3 stderr=STDOUT, cwd=path).communicate() Popen([GIT, 'config', 'user.name', 'Some User'], stdout=PIPE, stderr=STDOUT, cwd=path).communicate() + # Set HEAD back to master + Popen([GIT, 'checkout', 'master', '-q'], + stdout=PIPE, + stderr=STDOUT, + cwd=path).communicate() return True def _GetAskForDataCallback(self, expected_prompt, return_value): @@ -640,7 +645,7 @@ class ManagedGitWrapperTestCaseMock(unittest.TestCase): self, mockCheckOutput, mockExists, mockIsdir, mockClone): mockIsdir.side_effect = lambda path: path == self.base_path mockExists.side_effect = lambda path: path == self.base_path - mockCheckOutput.return_value = b'' + mockCheckOutput.side_effect = [b'refs/remotes/origin/main', b'', b''] options = self.Options() scm = gclient_scm.GitWrapper( @@ -648,18 +653,21 @@ class ManagedGitWrapperTestCaseMock(unittest.TestCase): scm.update(options, None, []) env = gclient_scm.scm.GIT.ApplyEnvVars({}) - self.assertEqual( - mockCheckOutput.mock_calls, - [ - mock.call( - ['git', '-c', 'core.quotePath=false', 'ls-files'], - cwd=self.base_path, env=env, stderr=-1), - mock.call( - ['git', 'rev-parse', '--verify', 'HEAD'], - cwd=self.base_path, env=env, stderr=-1), - ]) - mockClone.assert_called_with( - 'refs/remotes/origin/master', self.url, options) + self.assertEqual(mockCheckOutput.mock_calls, [ + mock.call(['git', 'symbolic-ref', 'refs/remotes/origin/HEAD'], + cwd=self.base_path, + env=env, + stderr=-1), + mock.call(['git', '-c', 'core.quotePath=false', 'ls-files'], + cwd=self.base_path, + env=env, + stderr=-1), + mock.call(['git', 'rev-parse', '--verify', 'HEAD'], + cwd=self.base_path, + env=env, + stderr=-1), + ]) + mockClone.assert_called_with('refs/remotes/origin/main', self.url, options) self.checkstdout('\n') @mock.patch('gclient_scm.GitWrapper._Clone') @@ -670,7 +678,7 @@ class ManagedGitWrapperTestCaseMock(unittest.TestCase): self, mockCheckOutput, mockExists, mockIsdir, mockClone): mockIsdir.side_effect = lambda path: path == self.base_path mockExists.side_effect = lambda path: path == self.base_path - mockCheckOutput.return_value = b'' + mockCheckOutput.side_effect = [b'refs/remotes/origin/main', b'', b''] mockClone.side_effect = [ gclient_scm.subprocess2.CalledProcessError( None, None, None, None, None), @@ -683,18 +691,21 @@ class ManagedGitWrapperTestCaseMock(unittest.TestCase): scm.update(options, None, []) env = gclient_scm.scm.GIT.ApplyEnvVars({}) - self.assertEqual( - mockCheckOutput.mock_calls, - [ - mock.call( - ['git', '-c', 'core.quotePath=false', 'ls-files'], - cwd=self.base_path, env=env, stderr=-1), - mock.call( - ['git', 'rev-parse', '--verify', 'HEAD'], - cwd=self.base_path, env=env, stderr=-1), - ]) - mockClone.assert_called_with( - 'refs/remotes/origin/master', self.url, options) + self.assertEqual(mockCheckOutput.mock_calls, [ + mock.call(['git', 'symbolic-ref', 'refs/remotes/origin/HEAD'], + cwd=self.base_path, + env=env, + stderr=-1), + mock.call(['git', '-c', 'core.quotePath=false', 'ls-files'], + cwd=self.base_path, + env=env, + stderr=-1), + mock.call(['git', 'rev-parse', '--verify', 'HEAD'], + cwd=self.base_path, + env=env, + stderr=-1), + ]) + mockClone.assert_called_with('refs/remotes/origin/main', self.url, options) self.checkstdout('\n') diff --git a/tests/scm_unittest.py b/tests/scm_unittest.py index 130e4f16b..fdff6b807 100755 --- a/tests/scm_unittest.py +++ b/tests/scm_unittest.py @@ -99,6 +99,26 @@ class GitWrapperTestCase(unittest.TestCase): r = scm.GIT.RemoteRefToRef(k, remote) self.assertEqual(r, v, msg='%s -> %s, expected %s' % (k, r, v)) + @mock.patch('scm.GIT.Capture') + @mock.patch('os.path.exists', lambda _:True) + def testGetRemoteHeadRefLocal(self, mockCapture): + mockCapture.side_effect = ['refs/remotes/origin/main'] + self.assertEqual('refs/remotes/origin/main', + scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin')) + self.assertEqual(mockCapture.call_count, 1) + + @mock.patch('scm.GIT.Capture') + @mock.patch('os.path.exists', lambda _:True) + def testGetRemoteHeadRefRemote(self, mockCapture): + mockCapture.side_effect = [ + subprocess2.CalledProcessError(1, '', '', '', ''), + 'ref: refs/heads/main\tHEAD\n' + + '0000000000000000000000000000000000000000\tHEAD', + ] + self.assertEqual('refs/remotes/origin/main', + scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin')) + self.assertEqual(mockCapture.call_count, 2) + class RealGitTest(fake_repos.FakeReposTestBase): def setUp(self): @@ -180,10 +200,10 @@ class RealGitTest(fake_repos.FakeReposTestBase): self.assertEqual( (None, None), scm.GIT.FetchUpstreamTuple(self.cwd)) - @mock.patch('scm.GIT.GetRemoteBranches', return_value=['origin/master']) + @mock.patch('scm.GIT.GetRemoteBranches', return_value=['origin/main']) def testFetchUpstreamTuple_GuessOriginMaster(self, _mockGetRemoteBranches): - self.assertEqual( - ('origin', 'refs/heads/master'), scm.GIT.FetchUpstreamTuple(self.cwd)) + self.assertEqual(('origin', 'refs/heads/main'), + scm.GIT.FetchUpstreamTuple(self.cwd)) @mock.patch('scm.GIT.GetRemoteBranches', return_value=['origin/master', 'origin/main'])