Skip to content

Commit bc74270

Browse files
committed
remote: allow specifying refs to prefetch
When using 'git fetch --prefetch', all fetchable refs are prefetched by default. In large repositories with many refs, this can lead to unnecessary network traffic and increased disk space use. Introduce a new configuration option 'remote.<name>.prefetchref' that allows users to specify specific ref patterns to be prefetched during a 'git fetch --prefetch' operation. The 'prefetchref' option accepts a space-separated list of ref patterns (e.g., 'refs/heads/main !refs/heads/feature/*'). When the '--prefetch' option is used with 'git fetch', only the refs matching these patterns will be prefetched. Signed-off-by: Shubham Kanodia <[email protected]>
1 parent 2e7b89e commit bc74270

File tree

5 files changed

+167
-8
lines changed

5 files changed

+167
-8
lines changed

Documentation/config/remote.txt

+7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ remote.<name>.fetch::
3333
The default set of "refspec" for linkgit:git-fetch[1]. See
3434
linkgit:git-fetch[1].
3535

36+
remote.<name>.prefetchref::
37+
Specify the refs to be prefetched when fetching from this
38+
remote. The value is a space-separated list of ref patterns
39+
(e.g., "refs/heads/main !refs/heads/develop*"). This can be
40+
used to optimize fetch operations by specifying exactly which
41+
refs should be prefetched.
42+
3643
remote.<name>.push::
3744
The default set of "refspec" for linkgit:git-push[1]. See
3845
linkgit:git-push[1].

builtin/fetch.c

+53
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,32 @@ static void filter_prefetch_refspec(struct refspec *rs)
485485
}
486486
}
487487

488+
static int pattern_matches_ref(const char *pattern, const char *refname)
489+
{
490+
if (strchr(pattern, '*'))
491+
return match_refspec_name_with_pattern(pattern, refname, NULL, NULL) != 0;
492+
return strcmp(pattern, refname) == 0;
493+
}
494+
495+
static int matches_prefetch_refs(const char *refname, const struct string_list *prefetch_refs)
496+
{
497+
int has_positive = 0, matched_positive = 0, matched_negative = 0;
498+
499+
for (int i = 0; i < prefetch_refs->nr; i++) {
500+
const char *pattern = prefetch_refs->items[i].string;
501+
int is_negative = (*pattern == '!');
502+
if (is_negative) pattern++;
503+
else has_positive = 1;
504+
505+
if (pattern_matches_ref(pattern, refname)) {
506+
if (is_negative) matched_negative = 1;
507+
else matched_positive = 1;
508+
}
509+
}
510+
511+
return has_positive ? (matched_positive && !matched_negative) : !matched_negative;
512+
}
513+
488514
static struct ref *get_ref_map(struct remote *remote,
489515
const struct ref *remote_refs,
490516
struct refspec *rs,
@@ -501,7 +527,11 @@ static struct ref *get_ref_map(struct remote *remote,
501527
struct hashmap existing_refs;
502528
int existing_refs_populated = 0;
503529

530+
struct ref *prefetch_filtered_ref_map = NULL, **ref_map_tail = &prefetch_filtered_ref_map;
531+
struct ref *next;
532+
504533
filter_prefetch_refspec(rs);
534+
505535
if (remote)
506536
filter_prefetch_refspec(&remote->fetch);
507537

@@ -610,6 +640,29 @@ static struct ref *get_ref_map(struct remote *remote,
610640
else
611641
ref_map = apply_negative_refspecs(ref_map, &remote->fetch);
612642

643+
/**
644+
* Filter out advertised refs that we don't want to fetch during
645+
* prefetch if a prefetchref config is set
646+
*/
647+
648+
if (prefetch && remote->prefetch_refs.nr) {
649+
prefetch_filtered_ref_map = NULL;
650+
ref_map_tail = &prefetch_filtered_ref_map;
651+
652+
for (rm = ref_map; rm; rm = next) {
653+
next = rm->next;
654+
rm->next = NULL;
655+
656+
if (matches_prefetch_refs(rm->name, &remote->prefetch_refs)) {
657+
*ref_map_tail = rm;
658+
ref_map_tail = &rm->next;
659+
} else {
660+
free_one_ref(rm);
661+
}
662+
}
663+
ref_map = prefetch_filtered_ref_map;
664+
}
665+
613666
ref_map = ref_remove_duplicates(ref_map);
614667

615668
for (rm = ref_map; rm; rm = rm->next) {

remote.c

+16-8
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ static struct remote *make_remote(struct remote_state *remote_state,
141141
ret->prune = -1; /* unspecified */
142142
ret->prune_tags = -1; /* unspecified */
143143
ret->name = xstrndup(name, len);
144+
string_list_init_dup(&ret->prefetch_refs);
144145
refspec_init(&ret->push, REFSPEC_PUSH);
145146
refspec_init(&ret->fetch, REFSPEC_FETCH);
146147

@@ -166,6 +167,7 @@ static void remote_clear(struct remote *remote)
166167
free((char *)remote->uploadpack);
167168
FREE_AND_NULL(remote->http_proxy);
168169
FREE_AND_NULL(remote->http_proxy_authmethod);
170+
string_list_clear(&remote->prefetch_refs, 0);
169171
}
170172

171173
static void add_merge(struct branch *branch, const char *name)
@@ -456,6 +458,12 @@ static int handle_config(const char *key, const char *value,
456458
remote->prune = git_config_bool(key, value);
457459
else if (!strcmp(subkey, "prunetags"))
458460
remote->prune_tags = git_config_bool(key, value);
461+
else if (!strcmp(subkey, "prefetchref")) {
462+
if (!value)
463+
return config_error_nonbool(key);
464+
string_list_split(&remote->prefetch_refs, value, ' ', -1);
465+
return 0;
466+
}
459467
else if (!strcmp(subkey, "url")) {
460468
if (!value)
461469
return config_error_nonbool(key);
@@ -868,7 +876,7 @@ struct strvec *push_url_of_remote(struct remote *remote)
868876
return remote->pushurl.nr ? &remote->pushurl : &remote->url;
869877
}
870878

871-
static int match_name_with_pattern(const char *key, const char *name,
879+
int match_refspec_name_with_pattern(const char *key, const char *name,
872880
const char *value, char **result)
873881
{
874882
const char *kstar = strchr(key, '*');
@@ -900,7 +908,7 @@ static int refspec_match(const struct refspec_item *refspec,
900908
const char *name)
901909
{
902910
if (refspec->pattern)
903-
return match_name_with_pattern(refspec->src, name, NULL, NULL);
911+
return match_refspec_name_with_pattern(refspec->src, name, NULL, NULL);
904912

905913
return !strcmp(refspec->src, name);
906914
}
@@ -969,7 +977,7 @@ static int query_matches_negative_refspec(struct refspec *rs, struct refspec_ite
969977
const char *key = refspec->dst ? refspec->dst : refspec->src;
970978
const char *value = refspec->src;
971979

972-
if (match_name_with_pattern(key, needle, value, &expn_name))
980+
if (match_refspec_name_with_pattern(key, needle, value, &expn_name))
973981
string_list_append_nodup(&reversed, expn_name);
974982
} else if (refspec->matching) {
975983
/* For the special matching refspec, any query should match */
@@ -1014,7 +1022,7 @@ static void query_refspecs_multiple(struct refspec *rs,
10141022
if (!refspec->dst || refspec->negative)
10151023
continue;
10161024
if (refspec->pattern) {
1017-
if (match_name_with_pattern(key, needle, value, result))
1025+
if (match_refspec_name_with_pattern(key, needle, value, result))
10181026
string_list_append_nodup(results, *result);
10191027
} else if (!strcmp(needle, key)) {
10201028
string_list_append(results, value);
@@ -1043,7 +1051,7 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)
10431051
if (!refspec->dst || refspec->negative)
10441052
continue;
10451053
if (refspec->pattern) {
1046-
if (match_name_with_pattern(key, needle, value, result)) {
1054+
if (match_refspec_name_with_pattern(key, needle, value, result)) {
10471055
query->force = refspec->force;
10481056
return 0;
10491057
}
@@ -1456,9 +1464,9 @@ static char *get_ref_match(const struct refspec *rs, const struct ref *ref,
14561464
const char *dst_side = item->dst ? item->dst : item->src;
14571465
int match;
14581466
if (direction == FROM_SRC)
1459-
match = match_name_with_pattern(item->src, ref->name, dst_side, &name);
1467+
match = match_refspec_name_with_pattern(item->src, ref->name, dst_side, &name);
14601468
else
1461-
match = match_name_with_pattern(dst_side, ref->name, item->src, &name);
1469+
match = match_refspec_name_with_pattern(dst_side, ref->name, item->src, &name);
14621470
if (match) {
14631471
matching_refs = i;
14641472
break;
@@ -2076,7 +2084,7 @@ static struct ref *get_expanded_map(const struct ref *remote_refs,
20762084

20772085
if (strchr(ref->name, '^'))
20782086
continue; /* a dereference item */
2079-
if (match_name_with_pattern(refspec->src, ref->name,
2087+
if (match_refspec_name_with_pattern(refspec->src, ref->name,
20802088
refspec->dst, &expn_name) &&
20812089
!ignore_symref_update(expn_name, &scratch)) {
20822090
struct ref *cpy = copy_ref(ref);

remote.h

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "hashmap.h"
66
#include "refspec.h"
77
#include "strvec.h"
8+
#include "string-list.h"
89

910
struct option;
1011
struct transport_ls_refs_options;
@@ -77,6 +78,8 @@ struct remote {
7778

7879
struct refspec fetch;
7980

81+
struct string_list prefetch_refs;
82+
8083
/*
8184
* The setting for whether to fetch tags (as a separate rule from the
8285
* configured refspecs);
@@ -207,6 +210,9 @@ int count_refspec_match(const char *, struct ref *refs, struct ref **matched_ref
207210

208211
int check_ref_type(const struct ref *ref, int flags);
209212

213+
int match_refspec_name_with_pattern(const char *key, const char *name,
214+
const char *value, char **result);
215+
210216
/*
211217
* Free a single ref and its peer, or an entire list of refs and their peers,
212218
* respectively.

t/t7900-maintenance.sh

+85
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,91 @@ test_expect_success 'prefetch multiple remotes' '
245245
test_subcommand git fetch remote2 $fetchargs <skip-remote1.txt
246246
'
247247

248+
test_expect_success 'prefetch with positive prefetch ref patterns' '
249+
test_create_repo filter-prefetch-positive &&
250+
(
251+
cd filter-prefetch-positive &&
252+
test_commit initial &&
253+
git clone . clone2 &&
254+
git remote add remote2 "file://$(pwd)/clone2" &&
255+
256+
cd clone2 &&
257+
git checkout -b feature && test_commit feature-commit-2 &&
258+
git checkout -b wip/test && test_commit wip-test-commit-2 &&
259+
git checkout -b topic/x && test_commit topic-x-commit-2 &&
260+
git push -f origin feature wip/test topic/x &&
261+
cd .. &&
262+
263+
git config remote.remote2.prefetchref "refs/heads/feature" &&
264+
fetchargs="--prefetch --prune --no-tags --no-write-fetch-head \
265+
--recurse-submodules=no --quiet" &&
266+
GIT_TRACE2_EVENT="$(pwd)/prefetch-positive.txt" \
267+
git maintenance run --task=prefetch 2>/dev/null &&
268+
test_subcommand git fetch remote2 $fetchargs <prefetch-positive.txt &&
269+
270+
git rev-parse refs/prefetch/remotes/remote2/feature &&
271+
test_must_fail git rev-parse refs/prefetch/remotes/remote2/wip/test &&
272+
test_must_fail git rev-parse refs/prefetch/remotes/remote2/topic/x
273+
)
274+
'
275+
276+
test_expect_success 'prefetch with negative prefetch ref patterns' '
277+
test_create_repo filter-prefetch-negative &&
278+
(
279+
cd filter-prefetch-negative &&
280+
test_commit initial &&
281+
git clone . clone3 &&
282+
git remote add remote3 "file://$(pwd)/clone3" &&
283+
cat .git/config &&
284+
285+
cd clone3 &&
286+
git checkout -b feature && test_commit feature-commit-3 &&
287+
git checkout -b wip/test && test_commit wip-test-commit-3 &&
288+
git checkout -b topic/x && test_commit topic-x-commit-3 &&
289+
git push -f origin feature wip/test topic/x &&
290+
cd .. &&
291+
292+
git config remote.remote3.prefetchref "!refs/heads/wip/*" &&
293+
fetchargs="--prefetch --prune --no-tags --no-write-fetch-head \
294+
--recurse-submodules=no --quiet" &&
295+
GIT_TRACE2_EVENT="$(pwd)/prefetch-negative.txt" \
296+
git maintenance run --task=prefetch 2>/dev/null &&
297+
test_subcommand git fetch remote3 $fetchargs <prefetch-negative.txt &&
298+
git rev-parse refs/prefetch/remotes/remote3/feature &&
299+
git rev-parse refs/prefetch/remotes/remote3/topic/x &&
300+
test_must_fail git rev-parse refs/prefetch/remotes/remote3/wip/test
301+
)
302+
'
303+
304+
test_expect_success 'prefetch with positive & negative prefetch ref patterns' '
305+
test_create_repo filter-prefetch-mixed &&
306+
(
307+
cd filter-prefetch-mixed &&
308+
test_commit initial &&
309+
git clone . clone4 &&
310+
git remote add remote4 "file://$(pwd)/clone4" &&
311+
312+
cd clone4 &&
313+
git checkout -b feature && test_commit feature-commit-4 &&
314+
git checkout -b topic/x && test_commit topic-x-commit-4 &&
315+
git checkout -b topic/y && test_commit topic-y-commit-4 &&
316+
git push -f origin feature topic/x topic/y &&
317+
cd .. &&
318+
319+
git config remote.remote4.prefetchref \
320+
"refs/heads/topic/* !refs/heads/topic/y" &&
321+
fetchargs="--prefetch --prune --no-tags --no-write-fetch-head \
322+
--recurse-submodules=no --quiet" &&
323+
GIT_TRACE2_EVENT="$(pwd)/prefetch-mixed.txt" \
324+
git maintenance run --task=prefetch 2>/dev/null &&
325+
test_subcommand git fetch remote4 $fetchargs <prefetch-mixed.txt &&
326+
327+
test_must_fail git rev-parse refs/prefetch/remotes/remote4/feature &&
328+
test_must_fail git rev-parse refs/prefetch/remotes/remote4/topic/y &&
329+
git rev-parse refs/prefetch/remotes/remote4/topic/x
330+
)
331+
'
332+
248333
test_expect_success 'loose-objects task' '
249334
# Repack everything so we know the state of the object dir
250335
git repack -adk &&

0 commit comments

Comments
 (0)