rebase: Resolve revisionExpr to tracking branch for --onto-manifest

When running `repo rebase -m` (`--onto-manifest`), the command uses the
raw `revisionExpr` from the manifest (e.g. `main`) directly as the
`--onto` target. This can fail or behave incorrectly if it should
reference the local tracking branch (e.g. `refs/remotes/<remote>/main`).

Resolve `project.revisionExpr` to its local tracking branch using
`project.GetRemote().ToLocal()`. Fall back to using the raw
`revisionExpr` value if the resolution fails (raising a `GitError`).

Bug: 532028666
Change-Id: I4c1bca1374a5842688be227f6aa2afffcdad5397
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/604941
Reviewed-by: Brian Gan <brgan@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
diff --git a/subcmds/rebase.py b/subcmds/rebase.py
index d7e769c..af8be4e 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -16,7 +16,9 @@
 
 from color import Coloring
 from command import Command
+from error import GitError
 from git_command import GitCommand
+from project import Project
 from repo_logging import RepoLogger
 
 
@@ -30,6 +32,17 @@
         self.fail = self.printer("fail", fg="red")
 
 
+def _ResolveOntoManifest(project: Project) -> str:
+    """Resolve project's revisionExpr to a local tracking branch.
+
+    Falls back to the raw revisionExpr if ToLocal fails or raises GitError.
+    """
+    try:
+        return project.GetRemote().ToLocal(project.revisionExpr)
+    except GitError:
+        return project.revisionExpr
+
+
 class Rebase(Command):
     COMMON = True
     helpSummary = "Rebase local branches on upstream branch"
@@ -162,7 +175,7 @@
             args = common_args[:]
             if opt.onto_manifest:
                 args.append("--onto")
-                args.append(project.revisionExpr)
+                args.append(_ResolveOntoManifest(project))
 
             args.append(upbranch.LocalMerge)
 
diff --git a/tests/test_subcmds_rebase.py b/tests/test_subcmds_rebase.py
new file mode 100644
index 0000000..d7e9173
--- /dev/null
+++ b/tests/test_subcmds_rebase.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2026 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Unittests for the subcmds/rebase.py module."""
+
+from unittest import mock
+
+from error import GitError
+from subcmds import rebase
+
+
+def test_resolve_onto_manifest_success() -> None:
+    """Test _ResolveOntoManifest when ToLocal succeeds."""
+    project = mock.MagicMock()
+    project.revisionExpr = "main"
+
+    remote = mock.MagicMock()
+    remote.ToLocal.return_value = "refs/remotes/goog/main"
+    project.GetRemote.return_value = remote
+
+    res = rebase._ResolveOntoManifest(project)
+    assert res == "refs/remotes/goog/main"
+    project.GetRemote.assert_called_once()
+    remote.ToLocal.assert_called_once_with("main")
+
+
+def test_resolve_onto_manifest_fallback() -> None:
+    """Test _ResolveOntoManifest when ToLocal raises GitError."""
+    project = mock.MagicMock()
+    project.revisionExpr = "main"
+
+    remote = mock.MagicMock()
+    remote.ToLocal.side_effect = GitError("Failed to resolve")
+    project.GetRemote.return_value = remote
+
+    res = rebase._ResolveOntoManifest(project)
+    assert res == "main"
+    project.GetRemote.assert_called_once()
+    remote.ToLocal.assert_called_once_with("main")