fetch-pack: do not lazy-fetch during ref iteration
In order to determine negotiation tips, "fetch-pack" iterates over all refs and dereferences all annotated tags found. This causes the existence of targets of refs and annotated tags to be checked. Avoiding this is especially important when we use "git fetch" (which invokes "fetch-pack") to perform lazy fetches in a partial clone because a target of such a ref or annotated tag may need to be itself lazy-fetched (and otherwise causing an infinite loop). Therefore, teach "fetch-pack" not to lazy fetch whenever iterating over refs. This is done by using the raw form of ref iteration and by dereferencing tags ourselves. Signed-off-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jonathan Tan committed
Aug 17, 2020 at 21:01 UTC
5c3b801dab9d29c63c6c929405f808f064c11b77
2 files changed
+64
-35
fetch-pack.c
+44
-35
@@ -108,24 +108,48 @@ static void for_each_cached_alternate(struct fetch_negotiator *negotiator,
108
cb(negotiator, cache.items[i]);
109
}
110
111
+static struct commit *deref_without_lazy_fetch(const struct object_id *oid,
112
+ int mark_tags_complete)
113
+{
114
+ enum object_type type;
115
+ struct object_info info = { .typep = &type };
116
+
117
+ while (1) {
118
+ if (oid_object_info_extended(the_repository, oid, &info,
119
+ OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK))
120
+ return NULL;
121
+ if (type == OBJ_TAG) {
122
+ struct tag *tag = (struct tag *)
123
+ parse_object(the_repository, oid);
124
+
125
+ if (!tag->tagged)
126
+ return NULL;
127
+ if (mark_tags_complete)
128
+ tag->object.flags |= COMPLETE;
129
+ oid = &tag->tagged->oid;
130
+ } else {
131
+ break;
132
+ }
133
+ }
134
+ if (type == OBJ_COMMIT)
135
+ return (struct commit *) parse_object(the_repository, oid);
136
+ return NULL;
137
+}
138
+
139
static int rev_list_insert_ref(struct fetch_negotiator *negotiator,
112
- const char *refname,
140
const struct object_id *oid)
141
{
115
- struct object *o = deref_tag(the_repository,
116
- parse_object(the_repository, oid),
117
- refname, 0);
118
-
119
- if (o && o->type == OBJ_COMMIT)
120
- negotiator->add_tip(negotiator, (struct commit *)o);
142
+ struct commit *c = deref_without_lazy_fetch(oid, 0);
143
144
+ if (c)
145
+ negotiator->add_tip(negotiator, c);
146
return 0;
147
}
148
149
static int rev_list_insert_ref_oid(const char *refname, const struct object_id *oid,
150
int flag, void *cb_data)
151
{
128
- return rev_list_insert_ref(cb_data, refname, oid);
152
+ return rev_list_insert_ref(cb_data, oid);
153
}
154
155
enum ack_type {
@@ -201,7 +225,7 @@ static void send_request(struct fetch_pack_args *args,
225
static void insert_one_alternate_object(struct fetch_negotiator *negotiator,
226
struct object *obj)
227
{
204
- rev_list_insert_ref(negotiator, NULL, &obj->oid);
228
+ rev_list_insert_ref(negotiator, &obj->oid);
229
}
230
231
#define INITIAL_FLUSH 16
@@ -230,13 +254,12 @@ static void mark_tips(struct fetch_negotiator *negotiator,
254
int i;
255
256
if (!negotiation_tips) {
233
- for_each_ref(rev_list_insert_ref_oid, negotiator);
257
+ for_each_rawref(rev_list_insert_ref_oid, negotiator);
258
return;
259
}
260
261
for (i = 0; i < negotiation_tips->nr; i++)
238
- rev_list_insert_ref(negotiator, NULL,
239
- &negotiation_tips->oid[i]);
262
+ rev_list_insert_ref(negotiator, &negotiation_tips->oid[i]);
263
return;
264
}
265
@@ -503,21 +526,11 @@ static struct commit_list *complete;
526
527
static int mark_complete(const struct object_id *oid)
528
{
506
- struct object *o = parse_object(the_repository, oid);
507
-
508
- while (o && o->type == OBJ_TAG) {
509
- struct tag *t = (struct tag *) o;
510
- if (!t->tagged)
511
- break; /* broken repository */
512
- o->flags |= COMPLETE;
513
- o = parse_object(the_repository, &t->tagged->oid);
514
- }
515
- if (o && o->type == OBJ_COMMIT) {
516
- struct commit *commit = (struct commit *)o;
517
- if (!(commit->object.flags & COMPLETE)) {
518
- commit->object.flags |= COMPLETE;
519
- commit_list_insert(commit, &complete);
520
- }
529
+ struct commit *commit = deref_without_lazy_fetch(oid, 1);
530
+
531
+ if (commit && !(commit->object.flags & COMPLETE)) {
532
+ commit->object.flags |= COMPLETE;
533
+ commit_list_insert(commit, &complete);
534
}
535
return 0;
536
}
@@ -702,7 +715,7 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
715
*/
716
trace2_region_enter("fetch-pack", "mark_complete_local_refs", NULL);
717
if (!args->deepen) {
705
- for_each_ref(mark_complete_oid, NULL);
718
+ for_each_rawref(mark_complete_oid, NULL);
719
for_each_cached_alternate(NULL, mark_alternate_complete);
720
commit_list_sort_by_date(&complete);
721
if (cutoff)
@@ -716,16 +729,12 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
729
*/
730
trace2_region_enter("fetch-pack", "mark_common_remote_refs", NULL);
731
for (ref = *refs; ref; ref = ref->next) {
719
- struct object *o = deref_tag(the_repository,
720
- lookup_object(the_repository,
721
- &ref->old_oid),
722
- NULL, 0);
732
+ struct commit *c = deref_without_lazy_fetch(&ref->old_oid, 0);
733
724
- if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
734
+ if (!c || !(c->object.flags & COMPLETE))
735
continue;
736
727
- negotiator->known_common(negotiator,
728
- (struct commit *)o);
737
+ negotiator->known_common(negotiator, c);
738
}
739
trace2_region_leave("fetch-pack", "mark_common_remote_refs", NULL);
740
t/t5616-partial-clone.sh
+20
@@ -417,6 +417,26 @@ test_expect_success 'fetch lazy-fetches only to resolve deltas, protocol v2' '
417
grep "want $(cat hash)" trace
418
'
419
420
+test_expect_success 'fetch does not lazy-fetch missing targets of its refs' '
421
+ rm -rf server client trace &&
422
+
423
+ test_create_repo server &&
424
+ test_config -C server uploadpack.allowfilter 1 &&
425
+ test_config -C server uploadpack.allowanysha1inwant 1 &&
426
+ test_commit -C server foo &&
427
+
428
+ git clone --filter=blob:none "file://$(pwd)/server" client &&
429
+ # Make all refs point to nothing by deleting all objects.
430
+ rm client/.git/objects/pack/* &&
431
+
432
+ test_commit -C server bar &&
433
+ GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
434
+ --no-tags --recurse-submodules=no \
435
+ origin refs/tags/bar &&
436
+ FOO_HASH=$(git -C server rev-parse foo) &&
437
+ ! grep "want $FOO_HASH" trace
438
+'
439
+
440
# The following two tests must be in this order. It is important that
441
# the srv.bare repository did not have tags during clone, but has tags
442
# in the fetch.