project: pass --no-filter and --no-tags in DownloadPatchSet In partial clone checkouts (e.g. --filter=blob:none or blob:limit=10k), DownloadPatchSet previously invoked `git fetch <remote> <ref>` without explicit filter overrides. Consequently, git fetch applied the configured partial clone filter from the remote and omitted the blobs for the incoming change. When repo download subsequently checks out, cherry-picks, or merges the downloaded commit, Git discovers that the required blobs are missing locally and triggers on-demand unadvertised object fetches to the promisor remote (POST /git-upload-pack with `want <blob_sha>`). On large repositories, server-side reachability validation for unadvertised objects can result in expensive graph traversals, proxy/gateway timeouts (such as HTTP 502 or connection drops), and client fetch failures: fatal: expected 'packfile' fatal: could not fetch <sha> from promisor remote Pass --no-filter (when Git >= 2.17.0) and --no-tags when fetching patch sets in DownloadPatchSet. Because the change ref is an advertised ref tip, fetching with --no-filter allows the server to generate a complete packfile containing all required blobs upfront without requiring unadvertised object reachability checks. All blobs are present in the local object database prior to checkout or cherry-pick, completely preventing on-demand promisor queries. TAG=agy CONV=bdc489e3-b20b-4ef3-b32e-549dbe21fc6c Google-Bug-Id: b/532920848 Test: pytest tests/test_project.py Change-Id: Ic6118e4edeb754f0467e0580fd1d0965a6553153 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/625621 Reviewed-by: Gavin Mak <gavinmak@google.com> Tested-by: Sam Saccone <samccone@google.com> Commit-Queue: Sam Saccone <samccone@google.com>
diff --git a/project.py b/project.py index 2d75d34..68986d4 100644 --- a/project.py +++ b/project.py
@@ -2218,7 +2218,11 @@ """Download a single patch set of a single change to FETCH_HEAD.""" remote = self.GetRemote() - cmd = ["fetch", remote.name] + cmd = ["fetch"] + if git_require((2, 17, 0)): + cmd.append("--no-filter") + cmd.append("--no-tags") + cmd.append(remote.name) cmd.append( "refs/changes/%2.2d/%d/%d" % (change_id % 100, change_id, patch_id) )
diff --git a/tests/test_project.py b/tests/test_project.py index 764fbc4..f82e1f6 100644 --- a/tests/test_project.py +++ b/tests/test_project.py
@@ -3661,3 +3661,63 @@ self._git(worktree, "status", "--porcelain").splitlines(), [" M keep.txt", "?? junk"], ) + + +class DownloadPatchSetTests(unittest.TestCase): + """Tests for Project.DownloadPatchSet.""" + + def setUp(self) -> None: + self.proj = mock.MagicMock() + self.proj.GetRevisionId.return_value = "base_rev_123" + self.proj.bare_git.rev_parse.return_value = "commit_sha_456" + mock_remote = mock.MagicMock() + mock_remote.name = "origin" + self.proj.GetRemote.return_value = mock_remote + + def test_download_patch_set_with_no_filter_and_no_tags(self) -> None: + """Test DownloadPatchSet includes --no-filter and --no-tags.""" + with mock.patch( + "project.git_require", return_value=True + ) as mock_git_require: + with mock.patch("project.GitCommand") as mock_git_cmd: + mock_cmd_instance = mock.MagicMock() + mock_cmd_instance.Wait.return_value = 0 + mock_git_cmd.return_value = mock_cmd_instance + + dl = project.Project.DownloadPatchSet(self.proj, 12345, 2) + + mock_git_require.assert_called_once_with((2, 17, 0)) + mock_git_cmd.assert_called_once_with( + self.proj, + [ + "fetch", + "--no-filter", + "--no-tags", + "origin", + "refs/changes/45/12345/2", + ], + bare=True, + verify_command=True, + ) + self.assertEqual(dl.change_id, 12345) + self.assertEqual(dl.ps_id, 2) + self.assertEqual(dl.commit, "commit_sha_456") + + def test_download_patch_set_legacy_git_omits_no_filter(self) -> None: + """Test DownloadPatchSet omits --no-filter on git < 2.17.""" + with mock.patch("project.git_require", return_value=False): + with mock.patch("project.GitCommand") as mock_git_cmd: + mock_cmd_instance = mock.MagicMock() + mock_cmd_instance.Wait.return_value = 0 + mock_git_cmd.return_value = mock_cmd_instance + + dl = project.Project.DownloadPatchSet(self.proj, 54321, 1) + + mock_git_cmd.assert_called_once_with( + self.proj, + ["fetch", "--no-tags", "origin", "refs/changes/21/54321/1"], + bare=True, + verify_command=True, + ) + self.assertEqual(dl.change_id, 54321) + self.assertEqual(dl.ps_id, 1)