447
return 0;
448
}
449
450
+struct bundles_for_sorting {
451
+ struct remote_bundle_info **items;
452
+ size_t alloc;
453
+ size_t nr;
454
+};
455
+
456
+static int append_bundle(struct remote_bundle_info *bundle, void *data)
457
+{
458
+ struct bundles_for_sorting *list = data;
459
+ list->items[list->nr++] = bundle;
460
+ return 0;
461
+}
462
+
463
+/**
464
+ * For use in QSORT() to get a list sorted by creationToken
465
+ * in decreasing order.
466
+ */
467
+static int compare_creation_token_decreasing(const void *va, const void *vb)
468
+{
469
+ const struct remote_bundle_info * const *a = va;
470
+ const struct remote_bundle_info * const *b = vb;
471
+
472
+ if ((*a)->creationToken > (*b)->creationToken)
473
+ return -1;
474
+ if ((*a)->creationToken < (*b)->creationToken)
475
+ return 1;
476
+ return 0;
477
+}
478
+
479
+static int fetch_bundles_by_token(struct repository *r,
480
+ struct bundle_list *list)
481
+{
482
+ int cur;
483
+ int move_direction = 0;
484
+ struct bundle_list_context ctx = {
485
+ .r = r,
486
+ .list = list,
487
+ .mode = list->mode,
488
+ };
489
+ struct bundles_for_sorting bundles = {
490
+ .alloc = hashmap_get_size(&list->bundles),
491
+ };
492
+
493
+ ALLOC_ARRAY(bundles.items, bundles.alloc);
494
+
495
+ for_all_bundles_in_list(list, append_bundle, &bundles);
496
+
497
+ QSORT(bundles.items, bundles.nr, compare_creation_token_decreasing);
498
+
499
+ /*
500
+ * Attempt to download and unbundle the minimum number of bundles by
501
+ * creationToken in decreasing order. If we fail to unbundle (after
502
+ * a successful download) then move to the next non-downloaded bundle
503
+ * and attempt downloading. Once we succeed in applying a bundle,
504
+ * move to the previous unapplied bundle and attempt to unbundle it
505
+ * again.
506
+ *
507
+ * In the case of a fresh clone, we will likely download all of the
508
+ * bundles before successfully unbundling the oldest one, then the
509
+ * rest of the bundles unbundle successfully in increasing order
510
+ * of creationToken.
511
+ *
512
+ * If there are existing objects, then this process may terminate
513
+ * early when all required commits from "new" bundles exist in the
514
+ * repo's object store.
515
+ */
516
+ cur = 0;
517
+ while (cur >= 0 && cur < bundles.nr) {
518
+ struct remote_bundle_info *bundle = bundles.items[cur];
519
+ if (!bundle->file) {
520
+ /*
521
+ * Not downloaded yet. Try downloading.
522
+ *
523
+ * Note that bundle->file is non-NULL if a download
524
+ * was attempted, even if it failed to download.
525
+ */
526
+ if (fetch_bundle_uri_internal(ctx.r, bundle, ctx.depth + 1, ctx.list)) {
527
+ /* Mark as unbundled so we do not retry. */
528
+ bundle->unbundled = 1;
529
+
530
+ /* Try looking deeper in the list. */
531
+ move_direction = 1;
532
+ goto move;
533
+ }
534
+
535
+ /* We expect bundles when using creationTokens. */
536
+ if (!is_bundle(bundle->file, 1)) {
537
+ warning(_("file downloaded from '%s' is not a bundle"),
538
+ bundle->uri);
539
+ break;
540
+ }
541
+ }
542
+
543
+ if (bundle->file && !bundle->unbundled) {
544
+ /*
545
+ * This was downloaded, but not successfully
546
+ * unbundled. Try unbundling again.
547
+ */
548
+ if (unbundle_from_file(ctx.r, bundle->file)) {
549
+ /* Try looking deeper in the list. */
550
+ move_direction = 1;
551
+ } else {
552
+ /*
553
+ * Succeeded in unbundle. Retry bundles
554
+ * that previously failed to unbundle.
555
+ */
556
+ move_direction = -1;
557
+ bundle->unbundled = 1;
558
+ }
559
+ }
560
+
561
+ /*
562
+ * Else case: downloaded and unbundled successfully.
563
+ * Skip this by moving in the same direction as the
564
+ * previous step.
565
+ */
566
+
567
+move:
568
+ /* Move in the specified direction and repeat. */
569
+ cur += move_direction;
570
+ }
571
+
572
+ free(bundles.items);
573
+
574
+ /*
575
+ * We succeed if the loop terminates because 'cur' drops below
576
+ * zero. The other case is that we terminate because 'cur'
577
+ * reaches the end of the list, so we have a failure no matter
578
+ * which bundles we apply from the list.
579
+ */
580
+ return cur >= 0;
581
+}
582
+
583
static int download_bundle_list(struct repository *r,
584
struct bundle_list *local_list,
585
struct bundle_list *global_list,
617
goto cleanup;
618
}
619
487
- if ((result = download_bundle_list(r, &list_from_bundle,
620
+ /*
621
+ * If this list uses the creationToken heuristic, then the URIs
622
+ * it advertises are expected to be bundles, not nested lists.
623
+ * We can drop 'global_list' and 'depth'.
624
+ */
625
+ if (list_from_bundle.heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN) {
626
+ result = fetch_bundles_by_token(r, &list_from_bundle);
627
+ global_list->heuristic = BUNDLE_HEURISTIC_CREATIONTOKEN;
628
+ } else if ((result = download_bundle_list(r, &list_from_bundle,
629
global_list, depth)))
630
goto cleanup;
631
767
int result;
768
struct bundle_list global_list;
769
770
+ /*
771
+ * If the creationToken heuristic is used, then the URIs
772
+ * advertised by 'list' are not nested lists and instead
773
+ * direct bundles. We do not need to use global_list.
774
+ */
775
+ if (list->heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN)
776
+ return fetch_bundles_by_token(r, list);
777
+
778
init_bundle_list(&global_list);
779
780
/* If a bundle is added to this global list, then it is required. */
783
if ((result = download_bundle_list(r, list, &global_list, 0)))
784
goto cleanup;
785
637
- result = unbundle_all_bundles(r, &global_list);
786
+ if (list->heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN)
787
+ result = fetch_bundles_by_token(r, list);
788
+ else
789
+ result = unbundle_all_bundles(r, &global_list);
790
791
cleanup:
792
for_all_bundles_in_list(&global_list, unlink_bundle, NULL);