From 091f5ac0a64ce66b75c185eb0ee698ef4e837145 Mon Sep 17 00:00:00 2001 From: Josip Sokcevic Date: Thu, 14 Jan 2021 23:14:21 +0000 Subject: [PATCH] Use real default branch in gclient Currently, gclient sync assumes the default branch is master, and it doesn't work at all if such branch doesn't exist. This change queries local git copy to get remote HEAD. If local git version is not available, it queries remote git server using ls-remote. This change requires git version 2.28 (depot_tools comes with 2.29). R=ehmaldonado@chromium.org Bug: 1156318 Change-Id: Id348e0f1004093f395139e8f4d62adb66b94ca9c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/2628359 Commit-Queue: Josip Sokcevic Reviewed-by: Edward Lesmes --- gclient_scm.py | 6 ++-- scm.py | 27 ++++++++++++++++- tests/gclient_scm_test.py | 63 +++++++++++++++++++++++---------------- tests/scm_unittest.py | 26 ++++++++++++++-- 4 files changed, 89 insertions(+), 33 deletions(-) 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'])