37
{
38
FREE_AND_NULL(bundle->id);
39
FREE_AND_NULL(bundle->uri);
40
+ FREE_AND_NULL(bundle->file);
41
+ bundle->unbundled = 0;
42
return 0;
43
}
44
336
return result;
337
}
338
339
+struct bundle_list_context {
340
+ struct repository *r;
341
+ struct bundle_list *list;
342
+ enum bundle_list_mode mode;
343
+ int count;
344
+ int depth;
345
+};
346
+
347
+/*
348
+ * This early definition is necessary because we use indirect recursion:
349
+ *
350
+ * While iterating through a bundle list that was downloaded as part
351
+ * of fetch_bundle_uri_internal(), iterator methods eventually call it
352
+ * again, but with depth + 1.
353
+ */
354
+static int fetch_bundle_uri_internal(struct repository *r,
355
+ struct remote_bundle_info *bundle,
356
+ int depth,
357
+ struct bundle_list *list);
358
+
359
+static int download_bundle_to_file(struct remote_bundle_info *bundle, void *data)
360
+{
361
+ int res;
362
+ struct bundle_list_context *ctx = data;
363
+
364
+ if (ctx->mode == BUNDLE_MODE_ANY && ctx->count)
365
+ return 0;
366
+
367
+ res = fetch_bundle_uri_internal(ctx->r, bundle, ctx->depth + 1, ctx->list);
368
+
369
+ /*
370
+ * Only increment count if the download succeeded. If our mode is
371
+ * BUNDLE_MODE_ANY, then we will want to try other URIs in the
372
+ * list in case they work instead.
373
+ */
374
+ if (!res)
375
+ ctx->count++;
376
+
377
+ /*
378
+ * To be opportunistic as possible, we continue iterating and
379
+ * download as many bundles as we can, so we can apply the ones
380
+ * that work, even in BUNDLE_MODE_ALL mode.
381
+ */
382
+ return 0;
383
+}
384
+
385
+static int download_bundle_list(struct repository *r,
386
+ struct bundle_list *local_list,
387
+ struct bundle_list *global_list,
388
+ int depth)
389
+{
390
+ struct bundle_list_context ctx = {
391
+ .r = r,
392
+ .list = global_list,
393
+ .depth = depth + 1,
394
+ .mode = local_list->mode,
395
+ };
396
+
397
+ return for_all_bundles_in_list(local_list, download_bundle_to_file, &ctx);
398
+}
399
+
400
+static int fetch_bundle_list_in_config_format(struct repository *r,
401
+ struct bundle_list *global_list,
402
+ struct remote_bundle_info *bundle,
403
+ int depth)
404
+{
405
+ int result;
406
+ struct bundle_list list_from_bundle;
407
+
408
+ init_bundle_list(&list_from_bundle);
409
+
410
+ if ((result = bundle_uri_parse_config_format(bundle->uri,
411
+ bundle->file,
412
+ &list_from_bundle)))
413
+ goto cleanup;
414
+
415
+ if (list_from_bundle.mode == BUNDLE_MODE_NONE) {
416
+ warning(_("unrecognized bundle mode from URI '%s'"),
417
+ bundle->uri);
418
+ result = -1;
419
+ goto cleanup;
420
+ }
421
+
422
+ if ((result = download_bundle_list(r, &list_from_bundle,
423
+ global_list, depth)))
424
+ goto cleanup;
425
+
426
+cleanup:
427
+ clear_bundle_list(&list_from_bundle);
428
+ return result;
429
+}
430
+
431
/**
432
* This limits the recursion on fetch_bundle_uri_internal() when following
433
* bundle lists.
434
*/
435
static int max_bundle_uri_depth = 4;
436
437
+/**
438
+ * Recursively download all bundles advertised at the given URI
439
+ * to files. If the file is a bundle, then add it to the given
440
+ * 'list'. Otherwise, expect a bundle list and recurse on the
441
+ * URIs in that list according to the list mode (ANY or ALL).
442
+ */
443
static int fetch_bundle_uri_internal(struct repository *r,
344
- const char *uri,
345
- int depth)
444
+ struct remote_bundle_info *bundle,
445
+ int depth,
446
+ struct bundle_list *list)
447
{
448
int result = 0;
348
- char *filename;
449
+ struct remote_bundle_info *bcopy;
450
451
if (depth >= max_bundle_uri_depth) {
452
warning(_("exceeded bundle URI recursion limit (%d)"),
454
return -1;
455
}
456
356
- if (!(filename = find_temp_filename())) {
457
+ if (!bundle->file &&
458
+ !(bundle->file = find_temp_filename())) {
459
result = -1;
460
goto cleanup;
461
}
462
361
- if ((result = copy_uri_to_file(filename, uri))) {
362
- warning(_("failed to download bundle from URI '%s'"), uri);
463
+ if ((result = copy_uri_to_file(bundle->file, bundle->uri))) {
464
+ warning(_("failed to download bundle from URI '%s'"), bundle->uri);
465
goto cleanup;
466
}
467
366
- if ((result = !is_bundle(filename, 0))) {
367
- warning(_("file at URI '%s' is not a bundle"), uri);
468
+ if ((result = !is_bundle(bundle->file, 1))) {
469
+ result = fetch_bundle_list_in_config_format(
470
+ r, list, bundle, depth);
471
+ if (result)
472
+ warning(_("file at URI '%s' is not a bundle or bundle list"),
473
+ bundle->uri);
474
goto cleanup;
475
}
476
371
- if ((result = unbundle_from_file(r, filename))) {
372
- warning(_("failed to unbundle bundle from URI '%s'"), uri);
373
- goto cleanup;
374
- }
477
+ /* Copy the bundle and insert it into the global list. */
478
+ CALLOC_ARRAY(bcopy, 1);
479
+ bcopy->id = xstrdup(bundle->id);
480
+ bcopy->file = xstrdup(bundle->file);
481
+ hashmap_entry_init(&bcopy->ent, strhash(bcopy->id));
482
+ hashmap_add(&list->bundles, &bcopy->ent);
483
484
cleanup:
377
- if (filename)
378
- unlink(filename);
379
- free(filename);
485
+ if (result && bundle->file)
486
+ unlink(bundle->file);
487
return result;
488
}
489
490
+/**
491
+ * This loop iterator breaks the loop with nonzero return code on the
492
+ * first successful unbundling of a bundle.
493
+ */
494
+static int attempt_unbundle(struct remote_bundle_info *info, void *data)
495
+{
496
+ struct repository *r = data;
497
+
498
+ if (!info->file || info->unbundled)
499
+ return 0;
500
+
501
+ if (!unbundle_from_file(r, info->file)) {
502
+ info->unbundled = 1;
503
+ return 1;
504
+ }
505
+
506
+ return 0;
507
+}
508
+
509
+static int unbundle_all_bundles(struct repository *r,
510
+ struct bundle_list *list)
511
+{
512
+ /*
513
+ * Iterate through all bundles looking for ones that can
514
+ * successfully unbundle. If any succeed, then perhaps another
515
+ * will succeed in the next attempt.
516
+ *
517
+ * Keep in mind that a non-zero result for the loop here means
518
+ * the loop terminated early on a successful unbundling, which
519
+ * signals that we can try again.
520
+ */
521
+ while (for_all_bundles_in_list(list, attempt_unbundle, r)) ;
522
+
523
+ return 0;
524
+}
525
+
526
+static int unlink_bundle(struct remote_bundle_info *info, void *data)
527
+{
528
+ if (info->file)
529
+ unlink_or_warn(info->file);
530
+ return 0;
531
+}
532
+
533
int fetch_bundle_uri(struct repository *r, const char *uri)
534
{
385
- return fetch_bundle_uri_internal(r, uri, 0);
535
+ int result;
536
+ struct bundle_list list;
537
+ struct remote_bundle_info bundle = {
538
+ .uri = xstrdup(uri),
539
+ .id = xstrdup(""),
540
+ };
541
+
542
+ init_bundle_list(&list);
543
+
544
+ /* If a bundle is added to this global list, then it is required. */
545
+ list.mode = BUNDLE_MODE_ALL;
546
+
547
+ if ((result = fetch_bundle_uri_internal(r, &bundle, 0, &list)))
548
+ goto cleanup;
549
+
550
+ result = unbundle_all_bundles(r, &list);
551
+
552
+cleanup:
553
+ for_all_bundles_in_list(&list, unlink_bundle, NULL);
554
+ clear_bundle_list(&list);
555
+ clear_remote_bundle_info(&bundle, NULL);
556
+ return result;
557
}
558
559
/**