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 <brgan@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
diff --git a/git_config.py b/git_config.py
index 888ae88..84d347b 100644
--- a/git_config.py
+++ b/git_config.py
@@ -40,7 +40,7 @@
 # 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 9583f3e..4b4e4ca 100644
--- a/tests/test_git_config.py
+++ b/tests/test_git_config.py
@@ -256,8 +256,8 @@
         ("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 57bb8c1..3be80c5 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -1039,6 +1039,58 @@
                     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.