project: derive HEAD fallback from git's own default branch

When the default branch cannot be determined dynamically, repo fell back
to a hardcoded "refs/heads/master". This can point at a branch that does
not exist on the server, since projects increasingly default to "main".
Rather than swap one hardcoded name for another, ask git itself what it
would use.

Bug: 483758905
Change-Id: Ic66712acea98f8e548a2d6d8211865ee5c416b4d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/612845
Tested-by: Brian Gan <brgan@google.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Brian Gan <brgan@google.com>
This commit is contained in:
Brian Gan authored and gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com committed 2026-07-30 17:00:00 -07:00
1 parent 29b6630a65
commit 54fa31cd84
2 files changed
+81 -5

No files matched your search

+25 -4
View File
@@ -15,6 +15,7 @@
import datetime
import errno
import filecmp
import functools
import glob
import os
import platform
@@ -4663,6 +4664,26 @@ class SyncBuffer:
self._pending_failures = []
@functools.lru_cache(maxsize=None)
def _DefaultBranchFallback() -> str:
"""Return the ref to use when remote default branch can't be resolved."""
def _git(args: List[str]) -> str:
p = GitCommand(
None,
args,
capture_stdout=True,
capture_stderr=True,
log_as_error=False,
)
return p.stdout.strip() if p.Wait() == 0 else ""
branch = _git(["var", "GIT_DEFAULT_BRANCH"]) or _git(
["config", "--get", "init.defaultBranch"]
)
return f"refs/heads/{branch or 'master'}"
class MetaProject(Project):
"""A special project housed under .repo."""
@@ -4676,7 +4697,7 @@ class MetaProject(Project):
worktree=worktree,
remote=RemoteSpec("origin"),
relpath=".repo/%s" % name,
revisionExpr="refs/heads/master",
revisionExpr=_DefaultBranchFallback(),
revisionId=None,
groups=None,
)
@@ -5134,9 +5155,9 @@ class ManifestProject(MetaProject):
if is_new:
default_branch = self.ResolveRemoteHead()
if default_branch is None:
# If the remote doesn't have HEAD configured, default to
# master.
default_branch = "refs/heads/master"
# If the remote doesn't have HEAD configured, fall back
# to whatever git uses as its default branch.
default_branch = _DefaultBranchFallback()
self.revisionExpr = default_branch
else:
self.PreSync()
+56 -1
View File
@@ -20,10 +20,11 @@ from pathlib import Path
import shutil
import subprocess
import tempfile
from typing import Optional
from typing import Dict, List, Optional, Tuple
import unittest
from unittest import mock
import pytest
import utils_for_test
import error
@@ -814,6 +815,60 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
self.assertFalse(os.path.exists(proj.gitdir))
_VAR_CMD: List[str] = ["var", "GIT_DEFAULT_BRANCH"]
_CONFIG_CMD: List[str] = ["config", "--get", "init.defaultBranch"]
@pytest.mark.parametrize(
"responses, expected_ref, expected_calls",
(
# git >= 2.35 answers `git var GIT_DEFAULT_BRANCH`.
({"var": (0, "jellybean\n")}, "refs/heads/jellybean", [_VAR_CMD]),
# Older git: `git var` fails, so read init.defaultBranch instead.
(
{"var": (1, ""), "config": (0, "custom\n")},
"refs/heads/custom",
[_VAR_CMD, _CONFIG_CMD],
),
# Nothing configured anywhere: git's historical built-in default.
(
{"var": (1, ""), "config": (1, "")},
"refs/heads/master",
[_VAR_CMD, _CONFIG_CMD],
),
),
ids=("git_var", "old_git_reads_config", "unconfigured_defaults_to_master"),
)
def test_default_branch_fallback(
responses: Dict[str, Tuple[int, str]],
expected_ref: str,
expected_calls: List[List[str]],
) -> None:
"""_DefaultBranchFallback resolves the default branch via git."""
seen: List[List[str]] = []
class FakeGitCommand:
# Emulate git by returning the canned response for the subcommand.
def __init__(
self, project_: Optional[project.Project], cmdv: List[str], **kwargs
) -> None:
self.returncode, self.stdout = responses[cmdv[0]]
seen.append(cmdv)
def Wait(self) -> int:
return self.returncode
# The result is memoized, so clear it before (to bypass any cached real
# value) and after (so the mocked value doesn't leak to other tests).
project._DefaultBranchFallback.cache_clear()
try:
with mock.patch.object(project, "GitCommand", FakeGitCommand):
assert project._DefaultBranchFallback() == expected_ref
assert seen == expected_calls
finally:
project._DefaultBranchFallback.cache_clear()
def _create_mock_project(
tempdir,
use_local_gitdirs=False,