Skip to content

Commit fa54ccc

Browse files
committed
Merge branch 'jt/non-blob-lazy-fetch'
A partial clone that is configured to lazily fetch missing objects will on-demand issue a "git fetch" request to the originating repository to fill not-yet-obtained objects. The request has been optimized for requesting a tree object (and not the leaf blob objects contained in it) by telling the originating repository that no blobs are needed. * jt/non-blob-lazy-fetch: fetch-pack: exclude blobs when lazy-fetching trees fetch-pack: avoid object flags if no_dependents
2 parents 2916cfe + 4c7f956 commit fa54ccc

File tree

3 files changed

+121
-42
lines changed

3 files changed

+121
-42
lines changed

fetch-pack.c

Lines changed: 73 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,10 @@ static int find_common(struct fetch_negotiator *negotiator,
253253
if (args->stateless_rpc && multi_ack == 1)
254254
die(_("--stateless-rpc requires multi_ack_detailed"));
255255

256-
mark_tips(negotiator, args->negotiation_tips);
257-
for_each_cached_alternate(negotiator, insert_one_alternate_object);
256+
if (!args->no_dependents) {
257+
mark_tips(negotiator, args->negotiation_tips);
258+
for_each_cached_alternate(negotiator, insert_one_alternate_object);
259+
}
258260

259261
fetching = 0;
260262
for ( ; refs ; refs = refs->next) {
@@ -271,8 +273,12 @@ static int find_common(struct fetch_negotiator *negotiator,
271273
* We use lookup_object here because we are only
272274
* interested in the case we *know* the object is
273275
* reachable and we have already scanned it.
276+
*
277+
* Do this only if args->no_dependents is false (if it is true,
278+
* we cannot trust the object flags).
274279
*/
275-
if (((o = lookup_object(the_repository, remote->hash)) != NULL) &&
280+
if (!args->no_dependents &&
281+
((o = lookup_object(the_repository, remote->hash)) != NULL) &&
276282
(o->flags & COMPLETE)) {
277283
continue;
278284
}
@@ -707,31 +713,29 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
707713

708714
oidset_clear(&loose_oid_set);
709715

710-
if (!args->no_dependents) {
711-
if (!args->deepen) {
712-
for_each_ref(mark_complete_oid, NULL);
713-
for_each_cached_alternate(NULL, mark_alternate_complete);
714-
commit_list_sort_by_date(&complete);
715-
if (cutoff)
716-
mark_recent_complete_commits(args, cutoff);
717-
}
716+
if (!args->deepen) {
717+
for_each_ref(mark_complete_oid, NULL);
718+
for_each_cached_alternate(NULL, mark_alternate_complete);
719+
commit_list_sort_by_date(&complete);
720+
if (cutoff)
721+
mark_recent_complete_commits(args, cutoff);
722+
}
718723

719-
/*
720-
* Mark all complete remote refs as common refs.
721-
* Don't mark them common yet; the server has to be told so first.
722-
*/
723-
for (ref = *refs; ref; ref = ref->next) {
724-
struct object *o = deref_tag(the_repository,
725-
lookup_object(the_repository,
726-
ref->old_oid.hash),
727-
NULL, 0);
724+
/*
725+
* Mark all complete remote refs as common refs.
726+
* Don't mark them common yet; the server has to be told so first.
727+
*/
728+
for (ref = *refs; ref; ref = ref->next) {
729+
struct object *o = deref_tag(the_repository,
730+
lookup_object(the_repository,
731+
ref->old_oid.hash),
732+
NULL, 0);
728733

729-
if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
730-
continue;
734+
if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
735+
continue;
731736

732-
negotiator->known_common(negotiator,
733-
(struct commit *)o);
734-
}
737+
negotiator->known_common(negotiator,
738+
(struct commit *)o);
735739
}
736740

737741
save_commit_buffer = old_save_commit_buffer;
@@ -987,11 +991,15 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
987991
if (!server_supports("deepen-relative") && args->deepen_relative)
988992
die(_("Server does not support --deepen"));
989993

990-
mark_complete_and_common_ref(&negotiator, args, &ref);
991-
filter_refs(args, &ref, sought, nr_sought);
992-
if (everything_local(args, &ref)) {
993-
packet_flush(fd[1]);
994-
goto all_done;
994+
if (!args->no_dependents) {
995+
mark_complete_and_common_ref(&negotiator, args, &ref);
996+
filter_refs(args, &ref, sought, nr_sought);
997+
if (everything_local(args, &ref)) {
998+
packet_flush(fd[1]);
999+
goto all_done;
1000+
}
1001+
} else {
1002+
filter_refs(args, &ref, sought, nr_sought);
9951003
}
9961004
if (find_common(&negotiator, args, fd, &oid, ref) < 0)
9971005
if (!args->keep_pack)
@@ -1037,7 +1045,7 @@ static void add_shallow_requests(struct strbuf *req_buf,
10371045
}
10381046
}
10391047

1040-
static void add_wants(const struct ref *wants, struct strbuf *req_buf)
1048+
static void add_wants(int no_dependents, const struct ref *wants, struct strbuf *req_buf)
10411049
{
10421050
int use_ref_in_want = server_supports_feature("fetch", "ref-in-want", 0);
10431051

@@ -1054,8 +1062,12 @@ static void add_wants(const struct ref *wants, struct strbuf *req_buf)
10541062
* We use lookup_object here because we are only
10551063
* interested in the case we *know* the object is
10561064
* reachable and we have already scanned it.
1065+
*
1066+
* Do this only if args->no_dependents is false (if it is true,
1067+
* we cannot trust the object flags).
10571068
*/
1058-
if (((o = lookup_object(the_repository, remote->hash)) != NULL) &&
1069+
if (!no_dependents &&
1070+
((o = lookup_object(the_repository, remote->hash)) != NULL) &&
10591071
(o->flags & COMPLETE)) {
10601072
continue;
10611073
}
@@ -1152,7 +1164,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
11521164
}
11531165

11541166
/* add wants */
1155-
add_wants(wants, &req_buf);
1167+
add_wants(args->no_dependents, wants, &req_buf);
11561168

11571169
if (args->no_dependents) {
11581170
packet_buf_write(&req_buf, "done");
@@ -1343,16 +1355,21 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
13431355
args->deepen = 1;
13441356

13451357
/* Filter 'ref' by 'sought' and those that aren't local */
1346-
mark_complete_and_common_ref(&negotiator, args, &ref);
1347-
filter_refs(args, &ref, sought, nr_sought);
1348-
if (everything_local(args, &ref))
1349-
state = FETCH_DONE;
1350-
else
1358+
if (!args->no_dependents) {
1359+
mark_complete_and_common_ref(&negotiator, args, &ref);
1360+
filter_refs(args, &ref, sought, nr_sought);
1361+
if (everything_local(args, &ref))
1362+
state = FETCH_DONE;
1363+
else
1364+
state = FETCH_SEND_REQUEST;
1365+
1366+
mark_tips(&negotiator, args->negotiation_tips);
1367+
for_each_cached_alternate(&negotiator,
1368+
insert_one_alternate_object);
1369+
} else {
1370+
filter_refs(args, &ref, sought, nr_sought);
13511371
state = FETCH_SEND_REQUEST;
1352-
1353-
mark_tips(&negotiator, args->negotiation_tips);
1354-
for_each_cached_alternate(&negotiator,
1355-
insert_one_alternate_object);
1372+
}
13561373
break;
13571374
case FETCH_SEND_REQUEST:
13581375
if (send_fetch_request(&negotiator, fd[1], args, ref,
@@ -1595,6 +1612,20 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
15951612
if (nr_sought)
15961613
nr_sought = remove_duplicates_in_refs(sought, nr_sought);
15971614

1615+
if (args->no_dependents && !args->filter_options.choice) {
1616+
/*
1617+
* The protocol does not support requesting that only the
1618+
* wanted objects be sent, so approximate this by setting a
1619+
* "blob:none" filter if no filter is already set. This works
1620+
* for all object types: note that wanted blobs will still be
1621+
* sent because they are directly specified as a "want".
1622+
*
1623+
* NEEDSWORK: Add an option in the protocol to request that
1624+
* only the wanted objects be sent, and implement it.
1625+
*/
1626+
parse_list_objects_filter(&args->filter_options, "blob:none");
1627+
}
1628+
15981629
if (!ref) {
15991630
packet_flush(fd[1]);
16001631
die(_("no matching remote head"));

fetch-pack.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ struct fetch_pack_args {
4343
unsigned from_promisor:1;
4444

4545
/*
46+
* Attempt to fetch only the wanted objects, and not any objects
47+
* referred to by them. Due to protocol limitations, extraneous
48+
* objects may still be included. (When fetching non-blob
49+
* objects, only blobs are excluded; when fetching a blob, the
50+
* blob itself will still be sent. The client does not need to
51+
* know whether a wanted object is a blob or not.)
52+
*
4653
* If 1, fetch_pack() will also not modify any object flags.
4754
* This allows fetch_pack() to safely be called by any function,
4855
* regardless of which object flags it uses (if any).

t/t0410-partial-clone.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,47 @@ test_expect_success 'fetching of missing objects works with ref-in-want enabled'
182182
grep "git< fetch=.*ref-in-want" trace
183183
'
184184

185+
test_expect_success 'fetching of missing blobs works' '
186+
rm -rf server repo &&
187+
test_create_repo server &&
188+
test_commit -C server foo &&
189+
git -C server repack -a -d --write-bitmap-index &&
190+
191+
git clone "file://$(pwd)/server" repo &&
192+
git hash-object repo/foo.t >blobhash &&
193+
rm -rf repo/.git/objects/* &&
194+
195+
git -C server config uploadpack.allowanysha1inwant 1 &&
196+
git -C server config uploadpack.allowfilter 1 &&
197+
git -C repo config core.repositoryformatversion 1 &&
198+
git -C repo config extensions.partialclone "origin" &&
199+
200+
git -C repo cat-file -p $(cat blobhash)
201+
'
202+
203+
test_expect_success 'fetching of missing trees does not fetch blobs' '
204+
rm -rf server repo &&
205+
test_create_repo server &&
206+
test_commit -C server foo &&
207+
git -C server repack -a -d --write-bitmap-index &&
208+
209+
git clone "file://$(pwd)/server" repo &&
210+
git -C repo rev-parse foo^{tree} >treehash &&
211+
git hash-object repo/foo.t >blobhash &&
212+
rm -rf repo/.git/objects/* &&
213+
214+
git -C server config uploadpack.allowanysha1inwant 1 &&
215+
git -C server config uploadpack.allowfilter 1 &&
216+
git -C repo config core.repositoryformatversion 1 &&
217+
git -C repo config extensions.partialclone "origin" &&
218+
git -C repo cat-file -p $(cat treehash) &&
219+
220+
# Ensure that the tree, but not the blob, is fetched
221+
git -C repo rev-list --objects --missing=print $(cat treehash) >objects &&
222+
grep "^$(cat treehash)" objects &&
223+
grep "^[?]$(cat blobhash)" objects
224+
'
225+
185226
test_expect_success 'rev-list stops traversal at missing and promised commit' '
186227
rm -rf repo &&
187228
test_create_repo repo &&

0 commit comments

Comments
 (0)