Use --negotiation-tip in superproject fetches.

Bug: b/260645739
Change-Id: Ib0cdbb13f130b91ab14df9c60a510f1e27cca8e0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/354354
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Joanna Wang <jojwang@google.com>
diff --git a/git_superproject.py b/git_superproject.py
index 8b6bbcf..7a4ca16 100644
--- a/git_superproject.py
+++ b/git_superproject.py
@@ -31,7 +31,7 @@
 
 from git_command import git_require, GitCommand
 from git_config import RepoConfig
-from git_refs import R_HEADS
+from git_refs import R_HEADS, GitRefs
 
 _SUPERPROJECT_GIT_NAME = 'superproject.git'
 _SUPERPROJECT_MANIFEST_NAME = 'superproject_override.xml'
@@ -181,6 +181,16 @@
       return False
     cmd = ['fetch', self._remote_url, '--depth', '1', '--force', '--no-tags',
            '--filter', 'blob:none']
+
+    # Check if there is a local ref that we can pass to --negotiation-tip.
+    # If this is the first fetch, it does not exist yet.
+    # We use --negotiation-tip to speed up the fetch. Superproject branches do
+    # not share commits. So this lets git know it only needs to send commits
+    # reachable from the specified local refs.
+    rev_commit = GitRefs(self._work_git).get(f'refs/heads/{self.revision}')
+    if rev_commit:
+      cmd.extend(['--negotiation-tip', rev_commit])
+
     if self._branch:
       cmd += [self._branch + ':' + self._branch]
     p = GitCommand(None,
diff --git a/tests/test_git_superproject.py b/tests/test_git_superproject.py
index 225e98c..49295d8 100644
--- a/tests/test_git_superproject.py
+++ b/tests/test_git_superproject.py
@@ -21,6 +21,7 @@
 import unittest
 from unittest import mock
 
+from git_command import GitCommand
 import git_superproject
 import git_trace2_event_log
 import manifest_xml
@@ -364,3 +365,41 @@
               'revision="52d3c9f7c107839ece2319d077de0cd922aa9d8f"/>'
               '<superproject name="superproject"/>'
               '</manifest>')
+
+  def test_Fetch(self):
+    manifest = self.getXmlManifest("""
+<manifest>
+  <remote name="default-remote" fetch="http://localhost" />
+  <default remote="default-remote" revision="refs/heads/main" />
+  <superproject name="superproject"/>
+  " /></manifest>
+""")
+    self.maxDiff = None
+    self._superproject = git_superproject.Superproject(
+        manifest, name='superproject',
+        remote=manifest.remotes.get('default-remote').ToRemoteSpec('superproject'),
+        revision='refs/heads/main')
+    os.mkdir(self._superproject._superproject_path)
+    os.mkdir(self._superproject._work_git)
+    with mock.patch.object(self._superproject, '_Init', return_value=True):
+      with mock.patch('git_superproject.GitCommand', autospec=True) as mock_git_command:
+        with mock.patch('git_superproject.GitRefs.get', autospec=True) as mock_git_refs:
+          instance = mock_git_command.return_value
+          instance.Wait.return_value = 0
+          mock_git_refs.side_effect = ['', '1234']
+
+          self.assertTrue(self._superproject._Fetch())
+          self.assertEqual(mock_git_command.call_args.args,(None, [
+              'fetch', 'http://localhost/superproject', '--depth', '1',
+              '--force', '--no-tags', '--filter', 'blob:none',
+              'refs/heads/main:refs/heads/main'
+          ]))
+
+          # If branch for revision exists, set as --negotiation-tip.
+          self.assertTrue(self._superproject._Fetch())
+          self.assertEqual(mock_git_command.call_args.args,(None, [
+              'fetch', 'http://localhost/superproject', '--depth', '1',
+              '--force', '--no-tags', '--filter', 'blob:none',
+              '--negotiation-tip', '1234',
+              'refs/heads/main:refs/heads/main'
+          ]))