Skip to content

Commit 0f5e885

Browse files
committed
Merge branch 'rc/fetch-refetch'
"git fetch --refetch" learned to fetch everything without telling the other side what we already have, which is useful when you cannot trust what you have in the local object store. * rc/fetch-refetch: docs: mention --refetch fetch option fetch: after refetch, encourage auto gc repacking t5615-partial-clone: add test for fetch --refetch fetch: add --refetch option builtin/fetch-pack: add --refetch option fetch-pack: add refetch fetch-negotiator: add specific noop initializer
2 parents 1b54f5b + 4963d3e commit 0f5e885

15 files changed

+197
-22
lines changed

Documentation/config/remote.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,7 @@ remote.<name>.promisor::
8282
objects.
8383

8484
remote.<name>.partialclonefilter::
85-
The filter that will be applied when fetching from this
86-
promisor remote.
85+
The filter that will be applied when fetching from this promisor remote.
86+
Changing or clearing this value will only affect fetches for new commits.
87+
To fetch associated objects for commits already present in the local object
88+
database, use the `--refetch` option of linkgit:git-fetch[1].

Documentation/fetch-options.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ endif::git-pull[]
163163
behavior for a remote may be specified with the remote.<name>.tagOpt
164164
setting. See linkgit:git-config[1].
165165

166+
ifndef::git-pull[]
167+
--refetch::
168+
Instead of negotiating with the server to avoid transferring commits and
169+
associated objects that are already present locally, this option fetches
170+
all objects as a fresh clone would. Use this to reapply a partial clone
171+
filter from configuration or using `--filter=` when the filter
172+
definition has changed. Automatic post-fetch maintenance will perform
173+
object database pack consolidation to remove any duplicate objects.
174+
endif::git-pull[]
175+
166176
--refmap=<refspec>::
167177
When fetching refs listed on the command line, use the
168178
specified refspec (can be given more than once) to map the

Documentation/git-fetch-pack.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ be in a separate packet, and the list must end with a flush packet.
101101
current shallow boundary instead of from the tip of each
102102
remote branch history.
103103

104+
--refetch::
105+
Skips negotiating commits with the server in order to fetch all matching
106+
objects. Use to reapply a new partial clone blob/tree filter.
107+
104108
--no-progress::
105109
Do not show the progress.
106110

Documentation/technical/partial-clone.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ Fetching Missing Objects
181181
currently fetches all objects referred to by the requested objects, even
182182
though they are not necessary.
183183

184+
- Fetching with `--refetch` will request a complete new filtered packfile from
185+
the remote, which can be used to change a filter without needing to
186+
dynamically fetch missing objects.
184187

185188
Using many promisor remotes
186189
---------------------------

builtin/fetch-pack.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
153153
args.from_promisor = 1;
154154
continue;
155155
}
156+
if (!strcmp("--refetch", arg)) {
157+
args.refetch = 1;
158+
continue;
159+
}
156160
if (skip_prefix(arg, ("--filter="), &arg)) {
157161
parse_list_objects_filter(&args.filter_options, arg);
158162
continue;

builtin/fetch.c

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ static int prune_tags = -1; /* unspecified */
5959

6060
static int all, append, dry_run, force, keep, multiple, update_head_ok;
6161
static int write_fetch_head = 1;
62-
static int verbosity, deepen_relative, set_upstream;
62+
static int verbosity, deepen_relative, set_upstream, refetch;
6363
static int progress = -1;
6464
static int enable_auto_gc = 1;
6565
static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
@@ -190,6 +190,9 @@ static struct option builtin_fetch_options[] = {
190190
OPT_SET_INT_F(0, "unshallow", &unshallow,
191191
N_("convert to a complete repository"),
192192
1, PARSE_OPT_NONEG),
193+
OPT_SET_INT_F(0, "refetch", &refetch,
194+
N_("re-fetch without negotiating common commits"),
195+
1, PARSE_OPT_NONEG),
193196
{ OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"),
194197
N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN },
195198
OPT_CALLBACK_F(0, "recurse-submodules-default",
@@ -1304,6 +1307,14 @@ static int check_exist_and_connected(struct ref *ref_map)
13041307
if (deepen)
13051308
return -1;
13061309

1310+
/*
1311+
* Similarly, if we need to refetch, we always want to perform a full
1312+
* fetch ignoring existing objects.
1313+
*/
1314+
if (refetch)
1315+
return -1;
1316+
1317+
13071318
/*
13081319
* check_connected() allows objects to merely be promised, but
13091320
* we need all direct targets to exist.
@@ -1517,6 +1528,8 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
15171528
set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, "yes");
15181529
if (update_shallow)
15191530
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
1531+
if (refetch)
1532+
set_option(transport, TRANS_OPT_REFETCH, "yes");
15201533
if (filter_options.choice) {
15211534
const char *spec =
15221535
expand_list_objects_filter_spec(&filter_options);
@@ -2293,8 +2306,25 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
22932306
NULL);
22942307
}
22952308

2296-
if (enable_auto_gc)
2309+
if (enable_auto_gc) {
2310+
if (refetch) {
2311+
/*
2312+
* Hint auto-maintenance strongly to encourage repacking,
2313+
* but respect config settings disabling it.
2314+
*/
2315+
int opt_val;
2316+
if (git_config_get_int("gc.autopacklimit", &opt_val))
2317+
opt_val = -1;
2318+
if (opt_val != 0)
2319+
git_config_push_parameter("gc.autoPackLimit=1");
2320+
2321+
if (git_config_get_int("maintenance.incremental-repack.auto", &opt_val))
2322+
opt_val = -1;
2323+
if (opt_val != 0)
2324+
git_config_push_parameter("maintenance.incremental-repack.auto=-1");
2325+
}
22972326
run_auto_maintenance(verbosity < 0);
2327+
}
22982328

22992329
cleanup:
23002330
string_list_clear(&list, 0);

fetch-negotiator.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,8 @@ void fetch_negotiator_init(struct repository *r,
2323
return;
2424
}
2525
}
26+
27+
void fetch_negotiator_init_noop(struct fetch_negotiator *negotiator)
28+
{
29+
noop_negotiator_init(negotiator);
30+
}

fetch-negotiator.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,15 @@ struct fetch_negotiator {
5353
void *data;
5454
};
5555

56+
/*
57+
* Initialize a negotiator based on the repository settings.
58+
*/
5659
void fetch_negotiator_init(struct repository *r,
5760
struct fetch_negotiator *negotiator);
5861

62+
/*
63+
* Initialize a noop negotiator.
64+
*/
65+
void fetch_negotiator_init_noop(struct fetch_negotiator *negotiator);
66+
5967
#endif

fetch-pack.c

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -312,19 +312,21 @@ static int find_common(struct fetch_negotiator *negotiator,
312312
const char *remote_hex;
313313
struct object *o;
314314

315-
/*
316-
* If that object is complete (i.e. it is an ancestor of a
317-
* local ref), we tell them we have it but do not have to
318-
* tell them about its ancestors, which they already know
319-
* about.
320-
*
321-
* We use lookup_object here because we are only
322-
* interested in the case we *know* the object is
323-
* reachable and we have already scanned it.
324-
*/
325-
if (((o = lookup_object(the_repository, remote)) != NULL) &&
326-
(o->flags & COMPLETE)) {
327-
continue;
315+
if (!args->refetch) {
316+
/*
317+
* If that object is complete (i.e. it is an ancestor of a
318+
* local ref), we tell them we have it but do not have to
319+
* tell them about its ancestors, which they already know
320+
* about.
321+
*
322+
* We use lookup_object here because we are only
323+
* interested in the case we *know* the object is
324+
* reachable and we have already scanned it.
325+
*/
326+
if (((o = lookup_object(the_repository, remote)) != NULL) &&
327+
(o->flags & COMPLETE)) {
328+
continue;
329+
}
328330
}
329331

330332
remote_hex = oid_to_hex(remote);
@@ -692,6 +694,9 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
692694
int old_save_commit_buffer = save_commit_buffer;
693695
timestamp_t cutoff = 0;
694696

697+
if (args->refetch)
698+
return;
699+
695700
save_commit_buffer = 0;
696701

697702
trace2_region_enter("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL);
@@ -1028,7 +1033,11 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
10281033
struct fetch_negotiator *negotiator;
10291034

10301035
negotiator = &negotiator_alloc;
1031-
fetch_negotiator_init(r, negotiator);
1036+
if (args->refetch) {
1037+
fetch_negotiator_init_noop(negotiator);
1038+
} else {
1039+
fetch_negotiator_init(r, negotiator);
1040+
}
10321041

10331042
sort_ref_list(&ref, ref_compare_name);
10341043
QSORT(sought, nr_sought, cmp_ref_by_name);
@@ -1121,7 +1130,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
11211130

11221131
mark_complete_and_common_ref(negotiator, args, &ref);
11231132
filter_refs(args, &ref, sought, nr_sought);
1124-
if (everything_local(args, &ref)) {
1133+
if (!args->refetch && everything_local(args, &ref)) {
11251134
packet_flush(fd[1]);
11261135
goto all_done;
11271136
}
@@ -1587,7 +1596,10 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
15871596
struct strvec index_pack_args = STRVEC_INIT;
15881597

15891598
negotiator = &negotiator_alloc;
1590-
fetch_negotiator_init(r, negotiator);
1599+
if (args->refetch)
1600+
fetch_negotiator_init_noop(negotiator);
1601+
else
1602+
fetch_negotiator_init(r, negotiator);
15911603

15921604
packet_reader_init(&reader, fd[0], NULL, 0,
15931605
PACKET_READ_CHOMP_NEWLINE |
@@ -1613,7 +1625,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
16131625
/* Filter 'ref' by 'sought' and those that aren't local */
16141626
mark_complete_and_common_ref(negotiator, args, &ref);
16151627
filter_refs(args, &ref, sought, nr_sought);
1616-
if (everything_local(args, &ref))
1628+
if (!args->refetch && everything_local(args, &ref))
16171629
state = FETCH_DONE;
16181630
else
16191631
state = FETCH_SEND_REQUEST;

fetch-pack.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ struct fetch_pack_args {
4242
unsigned update_shallow:1;
4343
unsigned reject_shallow_remote:1;
4444
unsigned deepen:1;
45+
unsigned refetch:1;
4546

4647
/*
4748
* Indicate that the remote of this request is a promisor remote. The

remote-curl.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ struct options {
4343
/* see documentation of corresponding flag in fetch-pack.h */
4444
from_promisor : 1,
4545

46+
refetch : 1,
4647
atomic : 1,
4748
object_format : 1,
4849
force_if_includes : 1;
@@ -198,6 +199,9 @@ static int set_option(const char *name, const char *value)
198199
} else if (!strcmp(name, "from-promisor")) {
199200
options.from_promisor = 1;
200201
return 0;
202+
} else if (!strcmp(name, "refetch")) {
203+
options.refetch = 1;
204+
return 0;
201205
} else if (!strcmp(name, "filter")) {
202206
options.filter = xstrdup(value);
203207
return 0;
@@ -1182,6 +1186,8 @@ static int fetch_git(struct discovery *heads,
11821186
strvec_push(&args, "--deepen-relative");
11831187
if (options.from_promisor)
11841188
strvec_push(&args, "--from-promisor");
1189+
if (options.refetch)
1190+
strvec_push(&args, "--refetch");
11851191
if (options.filter)
11861192
strvec_pushf(&args, "--filter=%s", options.filter);
11871193
strvec_push(&args, url.buf);

t/t5616-partial-clone.sh

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,85 @@ test_expect_success 'manual prefetch of missing objects' '
166166
test_line_count = 0 observed.oids
167167
'
168168

169+
# create new commits in "src" repo to establish a history on file.4.txt
170+
# and push to "srv.bare".
171+
test_expect_success 'push new commits to server for file.4.txt' '
172+
for x in a b c d e f
173+
do
174+
echo "Mod file.4.txt $x" >src/file.4.txt &&
175+
if list_contains "a,b" "$x"; then
176+
printf "%10000s" X >>src/file.4.txt
177+
fi &&
178+
if list_contains "c,d" "$x"; then
179+
printf "%20000s" X >>src/file.4.txt
180+
fi &&
181+
git -C src add file.4.txt &&
182+
git -C src commit -m "mod $x" || return 1
183+
done &&
184+
git -C src push -u srv main
185+
'
186+
187+
# Do partial fetch to fetch smaller files; then verify that without --refetch
188+
# applying a new filter does not refetch missing large objects. Then use
189+
# --refetch to apply the new filter on existing commits. Test it under both
190+
# protocol v2 & v0.
191+
test_expect_success 'apply a different filter using --refetch' '
192+
git -C pc1 fetch --filter=blob:limit=999 origin &&
193+
git -C pc1 rev-list --quiet --objects --missing=print \
194+
main..origin/main >observed &&
195+
test_line_count = 4 observed &&
196+
197+
git -C pc1 fetch --filter=blob:limit=19999 --refetch origin &&
198+
git -C pc1 rev-list --quiet --objects --missing=print \
199+
main..origin/main >observed &&
200+
test_line_count = 2 observed &&
201+
202+
git -c protocol.version=0 -C pc1 fetch --filter=blob:limit=29999 \
203+
--refetch origin &&
204+
git -C pc1 rev-list --quiet --objects --missing=print \
205+
main..origin/main >observed &&
206+
test_line_count = 0 observed
207+
'
208+
209+
test_expect_success 'fetch --refetch works with a shallow clone' '
210+
git clone --no-checkout --depth=1 --filter=blob:none "file://$(pwd)/srv.bare" pc1s &&
211+
git -C pc1s rev-list --objects --missing=print HEAD >observed &&
212+
test_line_count = 6 observed &&
213+
214+
GIT_TRACE=1 git -C pc1s fetch --filter=blob:limit=999 --refetch origin &&
215+
git -C pc1s rev-list --objects --missing=print HEAD >observed &&
216+
test_line_count = 6 observed
217+
'
218+
219+
test_expect_success 'fetch --refetch triggers repacking' '
220+
GIT_TRACE2_CONFIG_PARAMS=gc.autoPackLimit,maintenance.incremental-repack.auto &&
221+
export GIT_TRACE2_CONFIG_PARAMS &&
222+
223+
GIT_TRACE2_EVENT="$PWD/trace1.event" \
224+
git -C pc1 fetch --refetch origin &&
225+
test_subcommand git maintenance run --auto --no-quiet <trace1.event &&
226+
grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace1.event &&
227+
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace1.event &&
228+
229+
GIT_TRACE2_EVENT="$PWD/trace2.event" \
230+
git -c protocol.version=0 \
231+
-c gc.autoPackLimit=0 \
232+
-c maintenance.incremental-repack.auto=1234 \
233+
-C pc1 fetch --refetch origin &&
234+
test_subcommand git maintenance run --auto --no-quiet <trace2.event &&
235+
grep \"param\":\"gc.autopacklimit\",\"value\":\"0\" trace2.event &&
236+
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace2.event &&
237+
238+
GIT_TRACE2_EVENT="$PWD/trace3.event" \
239+
git -c protocol.version=0 \
240+
-c gc.autoPackLimit=1234 \
241+
-c maintenance.incremental-repack.auto=0 \
242+
-C pc1 fetch --refetch origin &&
243+
test_subcommand git maintenance run --auto --no-quiet <trace3.event &&
244+
grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace3.event &&
245+
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"0\" trace3.event
246+
'
247+
169248
test_expect_success 'partial clone with transfer.fsckobjects=1 works with submodules' '
170249
test_create_repo submodule &&
171250
test_commit -C submodule mycommit &&
@@ -225,7 +304,7 @@ test_expect_success 'use fsck before and after manually fetching a missing subtr
225304
226305
# Auto-fetch all remaining trees and blobs with --missing=error
227306
git -C dst rev-list --missing=error --objects main >fetched_objects &&
228-
test_line_count = 70 fetched_objects &&
307+
test_line_count = 88 fetched_objects &&
229308
230309
awk -f print_1.awk fetched_objects |
231310
xargs -n1 git -C dst cat-file -t >fetched_types &&

transport-helper.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ static int fetch_refs(struct transport *transport,
715715
if (data->transport_options.update_shallow)
716716
set_helper_option(transport, "update-shallow", "true");
717717

718+
if (data->transport_options.refetch)
719+
set_helper_option(transport, "refetch", "true");
720+
718721
if (data->transport_options.filter_options.choice) {
719722
const char *spec = expand_list_objects_filter_spec(
720723
&data->transport_options.filter_options);

transport.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ static int set_git_option(struct git_transport_options *opts,
250250
list_objects_filter_die_if_populated(&opts->filter_options);
251251
parse_list_objects_filter(&opts->filter_options, value);
252252
return 0;
253+
} else if (!strcmp(name, TRANS_OPT_REFETCH)) {
254+
opts->refetch = !!value;
255+
return 0;
253256
} else if (!strcmp(name, TRANS_OPT_REJECT_SHALLOW)) {
254257
opts->reject_shallow = !!value;
255258
return 0;
@@ -384,6 +387,7 @@ static int fetch_refs_via_pack(struct transport *transport,
384387
args.update_shallow = data->options.update_shallow;
385388
args.from_promisor = data->options.from_promisor;
386389
args.filter_options = data->options.filter_options;
390+
args.refetch = data->options.refetch;
387391
args.stateless_rpc = transport->stateless_rpc;
388392
args.server_options = transport->server_options;
389393
args.negotiation_tips = data->options.negotiation_tips;

0 commit comments

Comments
 (0)