cache-tree: prefetch in partial clone read-tree

"git read-tree" checks the existence of the blobs referenced by the given tree, but does not bulk prefetch them. Add a bulk prefetch. The lack of prefetch here was noticed at $DAYJOB during a merge involving some specific commits, but I couldn't find a minimal merge that didn't also trigger the prefetch in check_updates() in unpack-trees.c (and in all these cases, the lack of prefetch in cache-tree.c didn't matter because all the relevant blobs would have already been prefetched by then). This is why I used read-tree here to exercise this code path. Signed-off-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jonathan Tan committed Jul 23, 2021 at 11:52 UTC d3da223f2214ebc1527ccf66428aa975de916682
2 files changed +42 -2
cache-tree.c
+9 -2
@@ -237,6 +237,11 @@ int cache_tree_fully_valid(struct cache_tree *it)
237 return 1;
238 }
239
240 +static int must_check_existence(const struct cache_entry *ce)
241 +{
242 + return !(has_promisor_remote() && ce_skip_worktree(ce));
243 +}
244 +
245 static int update_one(struct cache_tree *it,
246 struct cache_entry **cache,
247 int entries,
@@ -378,8 +383,7 @@ static int update_one(struct cache_tree *it,
383 }
384
385 ce_missing_ok = mode == S_IFGITLINK || missing_ok ||
381 - (has_promisor_remote() &&
382 - ce_skip_worktree(ce));
386 + !must_check_existence(ce);
387 if (is_null_oid(oid) ||
388 (!ce_missing_ok && !has_object_file(oid))) {
389 strbuf_release(&buffer);
@@ -466,6 +470,9 @@ int cache_tree_update(struct index_state *istate, int flags)
470 if (!istate->cache_tree)
471 istate->cache_tree = cache_tree();
472
473 + if (!(flags & WRITE_TREE_MISSING_OK) && has_promisor_remote())
474 + prefetch_cache_entries(istate, must_check_existence);
475 +
476 trace_performance_enter();
477 trace2_region_enter("cache_tree", "update", the_repository);
478 i = update_one(istate->cache_tree, istate->cache, istate->cache_nr,
t/t1022-read-tree-partial-clone.sh new
+33
@@ -0,0 +1,33 @@
1 +#!/bin/sh
2 +
3 +test_description='git read-tree in partial clones'
4 +
5 +TEST_NO_CREATE_REPO=1
6 +
7 +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
8 +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
9 +
10 +. ./test-lib.sh
11 +
12 +test_expect_success 'read-tree in partial clone prefetches in one batch' '
13 + test_when_finished "rm -rf server client trace" &&
14 +
15 + git init server &&
16 + echo foo >server/one &&
17 + echo bar >server/two &&
18 + git -C server add one two &&
19 + git -C server commit -m "initial commit" &&
20 + TREE=$(git -C server rev-parse HEAD^{tree}) &&
21 +
22 + git -C server config uploadpack.allowfilter 1 &&
23 + git -C server config uploadpack.allowanysha1inwant 1 &&
24 + git clone --bare --filter=blob:none "file://$(pwd)/server" client &&
25 + GIT_TRACE_PACKET="$(pwd)/trace" git -C client read-tree $TREE &&
26 +
27 + # "done" marks the end of negotiation (once per fetch). Expect that
28 + # only one fetch occurs.
29 + grep "fetch> done" trace >donelines &&
30 + test_line_count = 1 donelines
31 +'
32 +
33 +test_done