Commit 091f5ac0 authored by Josip Sokcevic's avatar Josip Sokcevic Committed by LUCI CQ

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 <sokcevic@google.com>
Reviewed-by: 's avatarEdward Lesmes <ehmaldonado@chromium.org>
parent 2241db8a
......@@ -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()
......
......@@ -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'."""
......
......@@ -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')
......
......@@ -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'])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment