@matheusbsbs / matheus-bagliano / commits / e984c54ada
Fork 0

fetch: Speed up fetch by rewriting find_non_local_tags

When trying to get a list of remote tags to see if we need to fetch any we were doing a linear search for the matching tag ref for the tag^{} commit entries. This proves to be incredibly slow for large numbers of tags. Rewrite the function so that we build up a string_list of refs to fetch and then process that instead. As an extreme example, for a repository with 50000 tags (and just a single commit on a single branch), a fetch that does nothing goes from ~1m50s to ~4.1s. Signed-off-by: Julian Phillips <julian@quantumfyre.co.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Julian Phillips committed Sep 17, 2009 at 08:33 UTC e984c54ada086e7676419b6b1b8a2d7a2429da48
1 file changed +74 -33
builtin-fetch.c
+74 -33
@@ -504,57 +504,98 @@ static int will_fetch(struct ref **head, const unsigned char *sha1)
504 return 0;
505 }
506
507 +struct tag_data {
508 + struct ref **head;
509 + struct ref ***tail;
510 +};
511 +
512 +static int add_to_tail(struct string_list_item *item, void *cb_data)
513 +{
514 + struct tag_data *data = (struct tag_data *)cb_data;
515 + struct ref *rm = NULL;
516 +
517 + /* We have already decided to ignore this item */
518 + if (!item->util)
519 + return 0;
520 +
521 + rm = alloc_ref(item->string);
522 + rm->peer_ref = alloc_ref(item->string);
523 + hashcpy(rm->old_sha1, item->util);
524 +
525 + **data->tail = rm;
526 + *data->tail = &rm->next;
527 +
528 + return 0;
529 +}
530 +
531 static void find_non_local_tags(struct transport *transport,
532 struct ref **head,
533 struct ref ***tail)
534 {
535 struct string_list existing_refs = { NULL, 0, 0, 0 };
512 - struct string_list new_refs = { NULL, 0, 0, 1 };
513 - char *ref_name;
514 - int ref_name_len;
515 - const unsigned char *ref_sha1;
516 - const struct ref *tag_ref;
517 - struct ref *rm = NULL;
536 + struct string_list remote_refs = { NULL, 0, 0, 0 };
537 + struct tag_data data = {head, tail};
538 const struct ref *ref;
539 + struct string_list_item *item = NULL;
540
541 for_each_ref(add_existing, &existing_refs);
542 for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
543 if (prefixcmp(ref->name, "refs/tags"))
544 continue;
545
525 - ref_name = xstrdup(ref->name);
526 - ref_name_len = strlen(ref_name);
527 - ref_sha1 = ref->old_sha1;
528 -
529 - if (!strcmp(ref_name + ref_name_len - 3, "^{}")) {
530 - ref_name[ref_name_len - 3] = 0;
531 - tag_ref = transport_get_remote_refs(transport);
532 - while (tag_ref) {
533 - if (!strcmp(tag_ref->name, ref_name)) {
534 - ref_sha1 = tag_ref->old_sha1;
535 - break;
536 - }
537 - tag_ref = tag_ref->next;
538 - }
546 + /*
547 + * The peeled ref always follows the matching base
548 + * ref, so if we see a peeled ref that we don't want
549 + * to fetch then we can mark the ref entry in the list
550 + * as one to ignore by setting util to NULL.
551 + */
552 + if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) {
553 + if (item && !has_sha1_file(ref->old_sha1) &&
554 + !will_fetch(head, ref->old_sha1) &&
555 + !has_sha1_file(item->util) &&
556 + !will_fetch(head, item->util))
557 + item->util = NULL;
558 + item = NULL;
559 + continue;
560 }
561
541 - if (!string_list_has_string(&existing_refs, ref_name) &&
542 - !string_list_has_string(&new_refs, ref_name) &&
543 - (has_sha1_file(ref->old_sha1) ||
544 - will_fetch(head, ref->old_sha1))) {
545 - string_list_insert(ref_name, &new_refs);
562 + /*
563 + * If item is non-NULL here, then we previously saw a
564 + * ref not followed by a peeled reference, so we need
565 + * to check if it is a lightweight tag that we want to
566 + * fetch.
567 + */
568 + if (item && !has_sha1_file(item->util) &&
569 + !will_fetch(head, item->util))
570 + item->util = NULL;
571
547 - rm = alloc_ref(ref_name);
548 - rm->peer_ref = alloc_ref(ref_name);
549 - hashcpy(rm->old_sha1, ref_sha1);
572 + item = NULL;
573
551 - **tail = rm;
552 - *tail = &rm->next;
553 - }
554 - free(ref_name);
574 + /* skip duplicates and refs that we already have */
575 + if (string_list_has_string(&remote_refs, ref->name) ||
576 + string_list_has_string(&existing_refs, ref->name))
577 + continue;
578 +
579 + item = string_list_insert(ref->name, &remote_refs);
580 + item->util = (void *)ref->old_sha1;
581 }
582 string_list_clear(&existing_refs, 0);
557 - string_list_clear(&new_refs, 0);
583 +
584 + /*
585 + * We may have a final lightweight tag that needs to be
586 + * checked to see if it needs fetching.
587 + */
588 + if (item && !has_sha1_file(item->util) &&
589 + !will_fetch(head, item->util))
590 + item->util = NULL;
591 +
592 + /*
593 + * For all the tags in the remote_refs string list, call
594 + * add_to_tail to add them to the list of refs to be fetched
595 + */
596 + for_each_string_list(add_to_tail, &remote_refs, &data);
597 +
598 + string_list_clear(&remote_refs, 0);
599 }
600
601 static void check_not_current_branch(struct ref *ref_map)