From 948abc85bcfd3f73d6cec4160506af325f721b71 Mon Sep 17 00:00:00 2001 From: Gavin Mak Date: Tue, 25 Aug 2026 09:04:41 -0700 Subject: [PATCH] git_config: recognize only complete object IDs Full Git object IDs are exactly 40 hexadecimal digits for SHA-1 or 64 for SHA-256. Stop treating every intermediate length as immutable, which could bypass normal ref resolution for invalid revision strings. Bug: 553599402 Change-Id: I41b4ce6b2bfe2a2d8b351c7f040ec0cf469b474b Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/623901 Reviewed-by: Brian Gan Tested-by: Gavin Mak Commit-Queue: Gavin Mak --- git_config.py | 2 +- tests/test_git_config.py | 4 ++-- tests/test_project.py | 52 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/git_config.py b/git_config.py index 888ae888c..84d347b1a 100644 --- a/git_config.py +++ b/git_config.py @@ -40,7 +40,7 @@ from repo_trace import Trace # that is saved in the config. SYNC_STATE_PREFIX = "repo.syncstate." -ID_RE = re.compile(r"^[0-9a-f]{40,64}$") +ID_RE = re.compile(r"^(?:[0-9a-f]{40}|[0-9a-f]{64})$") REVIEW_CACHE = {} diff --git a/tests/test_git_config.py b/tests/test_git_config.py index 9583f3e9f..4b4e4cabb 100644 --- a/tests/test_git_config.py +++ b/tests/test_git_config.py @@ -256,8 +256,8 @@ def test_remote_save_with_push_url_without_projectname( ("0" * 64, True), ("f" * 64, True), ("a" * 39, False), - ("a" * 41, True), - ("a" * 63, True), + ("a" * 41, False), + ("a" * 63, False), ("a" * 65, False), ("g" * 40, False), ("g" * 64, False), diff --git a/tests/test_project.py b/tests/test_project.py index 57bb8c10c..3be80c5c2 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -1039,6 +1039,58 @@ class ProjectTests(unittest.TestCase): self._get_derived_subproject_url(submodule_url), ) + def test_set_revision_object_id_lengths(self) -> None: + """SetRevision only treats exact 40- or 64-char hex as immutable IDs.""" + with utils_for_test.TempGitTree() as tempdir: + proj = _create_mock_project(tempdir) + + # SHA-1 (40 hex chars) is recorded as revisionId directly. + sha1 = "a" * 40 + proj.SetRevision(sha1) + self.assertEqual(proj.revisionId, sha1) + + # SHA-256 (64 hex chars) is recorded as revisionId directly. + sha256 = "b" * 64 + proj.SetRevision(sha256) + self.assertEqual(proj.revisionId, sha256) + + # Intermediate hex strings (41-63 chars) must not be treated + # as commit IDs. + for length in (41, 48, 63): + proj.SetRevision("c" * length) + self.assertIsNone(proj.revisionId) + + def test_remote_fetch_intermediate_hex_not_fetched_as_commit_id( + self, + ) -> None: + """41-char hex revisions are not fetched as raw commit IDs on shallow + fetch.""" + with utils_for_test.TempGitTree() as tempdir: + proj = _create_mock_project(tempdir) + proj.config.GetRemote("origin").ResetFetch() + hex41 = "a" * 41 + proj.SetRevision(hex41) + + with mock.patch("project.GitCommand") as mock_git: + mock_cmd = mock.MagicMock() + mock_cmd.Wait.return_value = 0 + mock_git.return_value = mock_cmd + + proj._RemoteFetch(depth=1, current_branch_only=True) + + fetch_args = mock_git.call_args[0][1] + # When depth is set, commit IDs are passed directly to + # git fetch. + # Since 41 hex chars is not an ID, it must not appear as a + # standalone argument. + self.assertNotIn(hex41, fetch_args) + # Instead, it is treated as a branch ref and formatted + # as a refspec. + self.assertIn( + f"+refs/heads/{hex41}:refs/remotes/origin/{hex41}", + fetch_args, + ) + class CopyLinkTestCase(unittest.TestCase): """TestCase for stub repo client checkouts.