diff options
| author | Gavin Mak <gavinmak@google.com> | 2026-02-06 14:19:00 -0800 |
|---|---|---|
| committer | LUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2026-03-17 14:30:20 -0700 |
| commit | a0abfd7339536cfba02c112e7ac804bc6252ebdb (patch) | |
| tree | 0107ca0ca0be1d68f008dce610b6b97a187155e2 | |
| parent | 403fedfeb555d4e6c3144f36777e88ec41535d9d (diff) | |
| download | git-repo-a0abfd7339536cfba02c112e7ac804bc6252ebdb.tar.gz | |
project: resolve unborn HEAD robustly in reftable repos
Use `git symbolic-ref` to resolve HEAD before trying to parse .git/HEAD
directly which is unreliable for reftable repos.
Bug: 476209856
Change-Id: I60185d945c5b43c871945c0126cfdf52194e745d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/550762
Commit-Queue: Gavin Mak <gavinmak@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
| -rw-r--r-- | project.py | 18 | ||||
| -rw-r--r-- | tests/test_project.py | 27 |
2 files changed, 45 insertions, 0 deletions
diff --git a/project.py b/project.py index 3870ad379..caeaa5211 100644 --- a/project.py +++ b/project.py | |||
| @@ -3945,6 +3945,24 @@ class Project: | |||
| 3945 | return self.rev_parse(HEAD) | 3945 | return self.rev_parse(HEAD) |
| 3946 | return symbolic_head | 3946 | return symbolic_head |
| 3947 | except GitError as e: | 3947 | except GitError as e: |
| 3948 | # `git rev-parse --symbolic-full-name HEAD` will fail for unborn | ||
| 3949 | # branches, so try symbolic-ref before falling back to raw file | ||
| 3950 | # parsing. | ||
| 3951 | try: | ||
| 3952 | p = GitCommand( | ||
| 3953 | self._project, | ||
| 3954 | ["symbolic-ref", "-q", HEAD], | ||
| 3955 | bare=True, | ||
| 3956 | gitdir=self._gitdir, | ||
| 3957 | capture_stdout=True, | ||
| 3958 | capture_stderr=True, | ||
| 3959 | log_as_error=False, | ||
| 3960 | ) | ||
| 3961 | if p.Wait() == 0: | ||
| 3962 | return p.stdout.rstrip("\n") | ||
| 3963 | except GitError: | ||
| 3964 | pass | ||
| 3965 | |||
| 3948 | logger.warning( | 3966 | logger.warning( |
| 3949 | "project %s: unparseable HEAD; trying to recover.\n" | 3967 | "project %s: unparseable HEAD; trying to recover.\n" |
| 3950 | "Check that HEAD ref in .git/HEAD is valid. The error " | 3968 | "Check that HEAD ref in .git/HEAD is valid. The error " |
diff --git a/tests/test_project.py b/tests/test_project.py index 7c50ad52a..501707eaf 100644 --- a/tests/test_project.py +++ b/tests/test_project.py | |||
| @@ -19,6 +19,7 @@ import os | |||
| 19 | from pathlib import Path | 19 | from pathlib import Path |
| 20 | import subprocess | 20 | import subprocess |
| 21 | import tempfile | 21 | import tempfile |
| 22 | from typing import Optional | ||
| 22 | import unittest | 23 | import unittest |
| 23 | 24 | ||
| 24 | import utils_for_test | 25 | import utils_for_test |
| @@ -45,6 +46,9 @@ class FakeProject: | |||
| 45 | ) | 46 | ) |
| 46 | self.config = git_config.GitConfig.ForRepository(gitdir=self.gitdir) | 47 | self.config = git_config.GitConfig.ForRepository(gitdir=self.gitdir) |
| 47 | 48 | ||
| 49 | def RelPath(self, local: Optional[bool] = None) -> str: | ||
| 50 | return self.name | ||
| 51 | |||
| 48 | 52 | ||
| 49 | class ReviewableBranchTests(unittest.TestCase): | 53 | class ReviewableBranchTests(unittest.TestCase): |
| 50 | """Check ReviewableBranch behavior.""" | 54 | """Check ReviewableBranch behavior.""" |
| @@ -98,6 +102,29 @@ class ProjectTests(unittest.TestCase): | |||
| 98 | "abcd00%21%21_%2b", | 102 | "abcd00%21%21_%2b", |
| 99 | ) | 103 | ) |
| 100 | 104 | ||
| 105 | @unittest.skipUnless( | ||
| 106 | utils_for_test.supports_reftable(), | ||
| 107 | "git reftable support is required for this test", | ||
| 108 | ) | ||
| 109 | def test_get_head_unborn_reftable(self): | ||
| 110 | with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir: | ||
| 111 | subprocess.check_call( | ||
| 112 | [ | ||
| 113 | "git", | ||
| 114 | "-c", | ||
| 115 | "init.defaultRefFormat=reftable", | ||
| 116 | "init", | ||
| 117 | "-q", | ||
| 118 | tempdir, | ||
| 119 | ] | ||
| 120 | ) | ||
| 121 | fakeproj = FakeProject(tempdir) | ||
| 122 | expected = subprocess.check_output( | ||
| 123 | ["git", "-C", tempdir, "symbolic-ref", "-q", "HEAD"], | ||
| 124 | encoding="utf-8", | ||
| 125 | ).strip() | ||
| 126 | self.assertEqual(expected, fakeproj.work_git.GetHead()) | ||
| 127 | |||
| 101 | 128 | ||
| 102 | class CopyLinkTestCase(unittest.TestCase): | 129 | class CopyLinkTestCase(unittest.TestCase): |
| 103 | """TestCase for stub repo client checkouts. | 130 | """TestCase for stub repo client checkouts. |
