Skip to content

Commit d250f90

Browse files
committed
Merge branch 'ds/maintenance-prefetch-fix'
The prefetch task in "git maintenance" assumed that "git fetch" from any remote would fetch all its local branches, which would fetch too much if the user is interested in only a subset of branches there. * ds/maintenance-prefetch-fix: maintenance: respect remote.*.skipFetchAll maintenance: use 'git fetch --prefetch' fetch: add --prefetch option maintenance: simplify prefetch logic
2 parents a819e2b + 32f6788 commit d250f90

File tree

6 files changed

+134
-40
lines changed

6 files changed

+134
-40
lines changed

Documentation/fetch-options.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ ifndef::git-pull[]
110110
setting `fetch.writeCommitGraph`.
111111
endif::git-pull[]
112112

113+
--prefetch::
114+
Modify the configured refspec to place all refs into the
115+
`refs/prefetch/` namespace. See the `prefetch` task in
116+
linkgit:git-maintenance[1].
117+
113118
-p::
114119
--prune::
115120
Before fetching, remove any remote-tracking references that no

Documentation/git-maintenance.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,8 @@ commit-graph::
9292
prefetch::
9393
The `prefetch` task updates the object directory with the latest
9494
objects from all registered remotes. For each remote, a `git fetch`
95-
command is run. The refmap is custom to avoid updating local or remote
96-
branches (those in `refs/heads` or `refs/remotes`). Instead, the
97-
remote refs are stored in `refs/prefetch/<remote>/`. Also, tags are
98-
not updated.
95+
command is run. The configured refspec is modified to place all
96+
requested refs within `refs/prefetch/`. Also, tags are not updated.
9997
+
10098
This is done to avoid disrupting the remote-tracking branches. The end users
10199
expect these refs to stay unmoved unless they initiate a fetch. With prefetch

builtin/fetch.c

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ enum {
4848
static int fetch_prune_config = -1; /* unspecified */
4949
static int fetch_show_forced_updates = 1;
5050
static uint64_t forced_updates_ms = 0;
51+
static int prefetch = 0;
5152
static int prune = -1; /* unspecified */
5253
#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
5354

@@ -158,6 +159,8 @@ static struct option builtin_fetch_options[] = {
158159
N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
159160
OPT_INTEGER('j', "jobs", &max_jobs,
160161
N_("number of submodules fetched in parallel")),
162+
OPT_BOOL(0, "prefetch", &prefetch,
163+
N_("modify the refspec to place all refs within refs/prefetch/")),
161164
OPT_BOOL('p', "prune", &prune,
162165
N_("prune remote-tracking branches no longer on remote")),
163166
OPT_BOOL('P', "prune-tags", &prune_tags,
@@ -436,6 +439,56 @@ static void find_non_local_tags(const struct ref *refs,
436439
oidset_clear(&fetch_oids);
437440
}
438441

442+
static void filter_prefetch_refspec(struct refspec *rs)
443+
{
444+
int i;
445+
446+
if (!prefetch)
447+
return;
448+
449+
for (i = 0; i < rs->nr; i++) {
450+
struct strbuf new_dst = STRBUF_INIT;
451+
char *old_dst;
452+
const char *sub = NULL;
453+
454+
if (rs->items[i].negative)
455+
continue;
456+
if (!rs->items[i].dst ||
457+
(rs->items[i].src &&
458+
!strncmp(rs->items[i].src, "refs/tags/", 10))) {
459+
int j;
460+
461+
free(rs->items[i].src);
462+
free(rs->items[i].dst);
463+
464+
for (j = i + 1; j < rs->nr; j++) {
465+
rs->items[j - 1] = rs->items[j];
466+
rs->raw[j - 1] = rs->raw[j];
467+
}
468+
rs->nr--;
469+
i--;
470+
continue;
471+
}
472+
473+
old_dst = rs->items[i].dst;
474+
strbuf_addstr(&new_dst, "refs/prefetch/");
475+
476+
/*
477+
* If old_dst starts with "refs/", then place
478+
* sub after that prefix. Otherwise, start at
479+
* the beginning of the string.
480+
*/
481+
if (!skip_prefix(old_dst, "refs/", &sub))
482+
sub = old_dst;
483+
strbuf_addstr(&new_dst, sub);
484+
485+
rs->items[i].dst = strbuf_detach(&new_dst, NULL);
486+
rs->items[i].force = 1;
487+
488+
free(old_dst);
489+
}
490+
}
491+
439492
static struct ref *get_ref_map(struct remote *remote,
440493
const struct ref *remote_refs,
441494
struct refspec *rs,
@@ -452,6 +505,10 @@ static struct ref *get_ref_map(struct remote *remote,
452505
struct hashmap existing_refs;
453506
int existing_refs_populated = 0;
454507

508+
filter_prefetch_refspec(rs);
509+
if (remote)
510+
filter_prefetch_refspec(&remote->fetch);
511+
455512
if (rs->nr) {
456513
struct refspec *fetch_refspec;
457514

@@ -520,7 +577,7 @@ static struct ref *get_ref_map(struct remote *remote,
520577
if (has_merge &&
521578
!strcmp(branch->remote_name, remote->name))
522579
add_merge_config(&ref_map, remote_refs, branch, &tail);
523-
} else {
580+
} else if (!prefetch) {
524581
ref_map = get_remote_ref(remote_refs, "HEAD");
525582
if (!ref_map)
526583
die(_("Couldn't find remote ref HEAD"));

builtin/gc.c

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -873,55 +873,40 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)
873873
return 0;
874874
}
875875

876-
static int fetch_remote(const char *remote, struct maintenance_run_opts *opts)
876+
static int fetch_remote(struct remote *remote, void *cbdata)
877877
{
878+
struct maintenance_run_opts *opts = cbdata;
878879
struct child_process child = CHILD_PROCESS_INIT;
879880

881+
if (remote->skip_default_update)
882+
return 0;
883+
880884
child.git_cmd = 1;
881-
strvec_pushl(&child.args, "fetch", remote, "--prune", "--no-tags",
885+
strvec_pushl(&child.args, "fetch", remote->name,
886+
"--prefetch", "--prune", "--no-tags",
882887
"--no-write-fetch-head", "--recurse-submodules=no",
883-
"--refmap=", NULL);
888+
NULL);
884889

885890
if (opts->quiet)
886891
strvec_push(&child.args, "--quiet");
887892

888-
strvec_pushf(&child.args, "+refs/heads/*:refs/prefetch/%s/*", remote);
889-
890893
return !!run_command(&child);
891894
}
892895

893-
static int append_remote(struct remote *remote, void *cbdata)
894-
{
895-
struct string_list *remotes = (struct string_list *)cbdata;
896-
897-
string_list_append(remotes, remote->name);
898-
return 0;
899-
}
900-
901896
static int maintenance_task_prefetch(struct maintenance_run_opts *opts)
902897
{
903-
int result = 0;
904-
struct string_list_item *item;
905-
struct string_list remotes = STRING_LIST_INIT_DUP;
906-
907898
git_config_set_multivar_gently("log.excludedecoration",
908899
"refs/prefetch/",
909900
"refs/prefetch/",
910901
CONFIG_FLAGS_FIXED_VALUE |
911902
CONFIG_FLAGS_MULTI_REPLACE);
912903

913-
if (for_each_remote(append_remote, &remotes)) {
914-
error(_("failed to fill remotes"));
915-
result = 1;
916-
goto cleanup;
904+
if (for_each_remote(fetch_remote, opts)) {
905+
error(_("failed to prefetch remotes"));
906+
return 1;
917907
}
918908

919-
for_each_string_list_item(item, &remotes)
920-
result |= fetch_remote(item->string, opts);
921-
922-
cleanup:
923-
string_list_clear(&remotes, 0);
924-
return result;
909+
return 0;
925910
}
926911

927912
static int maintenance_task_gc(struct maintenance_run_opts *opts)

t/t5582-fetch-negative-refspec.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,47 @@ test_expect_success "push with matching +: and negative refspec" '
240240
git -C two push -v one
241241
'
242242

243+
test_expect_success '--prefetch correctly modifies refspecs' '
244+
git -C one config --unset-all remote.origin.fetch &&
245+
git -C one config --add remote.origin.fetch ^refs/heads/bogus/ignore &&
246+
git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" &&
247+
git -C one config --add remote.origin.fetch "refs/heads/bogus/*:bogus/*" &&
248+
249+
git tag -a -m never never-fetch-tag HEAD &&
250+
251+
git branch bogus/fetched HEAD~1 &&
252+
git branch bogus/ignore HEAD &&
253+
254+
git -C one fetch --prefetch --no-tags &&
255+
test_must_fail git -C one rev-parse never-fetch-tag &&
256+
git -C one rev-parse refs/prefetch/bogus/fetched &&
257+
test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore &&
258+
259+
# correctly handle when refspec set becomes empty
260+
# after removing the refs/tags/* refspec.
261+
git -C one config --unset-all remote.origin.fetch &&
262+
git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" &&
263+
264+
git -C one fetch --prefetch --no-tags &&
265+
test_must_fail git -C one rev-parse never-fetch-tag &&
266+
267+
# The refspec for refs that are not fully qualified
268+
# are filtered multiple times.
269+
git -C one rev-parse refs/prefetch/bogus/fetched &&
270+
test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore
271+
'
272+
273+
test_expect_success '--prefetch succeeds when refspec becomes empty' '
274+
git checkout bogus/fetched &&
275+
test_commit extra &&
276+
277+
git -C one config --unset-all remote.origin.fetch &&
278+
git -C one config --unset branch.main.remote &&
279+
git -C one config remote.origin.fetch "+refs/tags/extra" &&
280+
git -C one config remote.origin.skipfetchall true &&
281+
git -C one config remote.origin.tagopt "--no-tags" &&
282+
283+
git -C one fetch --prefetch
284+
'
285+
243286
test_done

t/t7900-maintenance.sh

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,19 +141,25 @@ test_expect_success 'prefetch multiple remotes' '
141141
test_commit -C clone1 one &&
142142
test_commit -C clone2 two &&
143143
GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null &&
144-
fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" &&
145-
test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt &&
146-
test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt &&
144+
fetchargs="--prefetch --prune --no-tags --no-write-fetch-head --recurse-submodules=no --quiet" &&
145+
test_subcommand git fetch remote1 $fetchargs <run-prefetch.txt &&
146+
test_subcommand git fetch remote2 $fetchargs <run-prefetch.txt &&
147147
test_path_is_missing .git/refs/remotes &&
148-
git log prefetch/remote1/one &&
149-
git log prefetch/remote2/two &&
148+
git log prefetch/remotes/remote1/one &&
149+
git log prefetch/remotes/remote2/two &&
150150
git fetch --all &&
151-
test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one &&
152-
test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two &&
151+
test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one &&
152+
test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two &&
153153
154154
test_cmp_config refs/prefetch/ log.excludedecoration &&
155155
git log --oneline --decorate --all >log &&
156-
! grep "prefetch" log
156+
! grep "prefetch" log &&
157+
158+
test_when_finished git config --unset remote.remote1.skipFetchAll &&
159+
git config remote.remote1.skipFetchAll true &&
160+
GIT_TRACE2_EVENT="$(pwd)/skip-remote1.txt" git maintenance run --task=prefetch 2>/dev/null &&
161+
test_subcommand ! git fetch remote1 $fetchargs <skip-remote1.txt &&
162+
test_subcommand git fetch remote2 $fetchargs <skip-remote1.txt
157163
'
158164

159165
test_expect_success 'prefetch and existing log.excludeDecoration values' '

0 commit comments

Comments
 (0)