Coverage Report

Created: 2024-09-08 06:23

/src/git/bundle-uri.c
Line
Count
Source (jump to first uncovered line)
1
#define USE_THE_REPOSITORY_VARIABLE
2
3
#include "git-compat-util.h"
4
#include "bundle-uri.h"
5
#include "bundle.h"
6
#include "copy.h"
7
#include "environment.h"
8
#include "gettext.h"
9
#include "refs.h"
10
#include "run-command.h"
11
#include "hashmap.h"
12
#include "pkt-line.h"
13
#include "config.h"
14
#include "fetch-pack.h"
15
#include "remote.h"
16
#include "trace2.h"
17
18
static struct {
19
  enum bundle_list_heuristic heuristic;
20
  const char *name;
21
} heuristics[BUNDLE_HEURISTIC__COUNT] = {
22
  { BUNDLE_HEURISTIC_NONE, ""},
23
  { BUNDLE_HEURISTIC_CREATIONTOKEN, "creationToken" },
24
};
25
26
static int compare_bundles(const void *hashmap_cmp_fn_data UNUSED,
27
         const struct hashmap_entry *he1,
28
         const struct hashmap_entry *he2,
29
         const void *id)
30
0
{
31
0
  const struct remote_bundle_info *e1 =
32
0
    container_of(he1, const struct remote_bundle_info, ent);
33
0
  const struct remote_bundle_info *e2 =
34
0
    container_of(he2, const struct remote_bundle_info, ent);
35
36
0
  return strcmp(e1->id, id ? (const char *)id : e2->id);
37
0
}
38
39
void init_bundle_list(struct bundle_list *list)
40
0
{
41
0
  memset(list, 0, sizeof(*list));
42
43
  /* Implied defaults. */
44
0
  list->mode = BUNDLE_MODE_ALL;
45
0
  list->version = 1;
46
47
0
  hashmap_init(&list->bundles, compare_bundles, NULL, 0);
48
0
}
49
50
static int clear_remote_bundle_info(struct remote_bundle_info *bundle,
51
            void *data UNUSED)
52
0
{
53
0
  FREE_AND_NULL(bundle->id);
54
0
  FREE_AND_NULL(bundle->uri);
55
0
  FREE_AND_NULL(bundle->file);
56
0
  bundle->unbundled = 0;
57
0
  return 0;
58
0
}
59
60
void clear_bundle_list(struct bundle_list *list)
61
0
{
62
0
  if (!list)
63
0
    return;
64
65
0
  for_all_bundles_in_list(list, clear_remote_bundle_info, NULL);
66
0
  hashmap_clear_and_free(&list->bundles, struct remote_bundle_info, ent);
67
0
  free(list->baseURI);
68
0
}
69
70
int for_all_bundles_in_list(struct bundle_list *list,
71
          bundle_iterator iter,
72
          void *data)
73
0
{
74
0
  struct remote_bundle_info *info;
75
0
  struct hashmap_iter i;
76
77
0
  hashmap_for_each_entry(&list->bundles, &i, info, ent) {
78
0
    int result = iter(info, data);
79
80
0
    if (result)
81
0
      return result;
82
0
  }
83
84
0
  return 0;
85
0
}
86
87
static int summarize_bundle(struct remote_bundle_info *info, void *data)
88
0
{
89
0
  FILE *fp = data;
90
0
  fprintf(fp, "[bundle \"%s\"]\n", info->id);
91
0
  fprintf(fp, "\turi = %s\n", info->uri);
92
93
0
  if (info->creationToken)
94
0
    fprintf(fp, "\tcreationToken = %"PRIu64"\n", info->creationToken);
95
0
  return 0;
96
0
}
97
98
void print_bundle_list(FILE *fp, struct bundle_list *list)
99
0
{
100
0
  const char *mode;
101
102
0
  switch (list->mode) {
103
0
  case BUNDLE_MODE_ALL:
104
0
    mode = "all";
105
0
    break;
106
107
0
  case BUNDLE_MODE_ANY:
108
0
    mode = "any";
109
0
    break;
110
111
0
  case BUNDLE_MODE_NONE:
112
0
  default:
113
0
    mode = "<unknown>";
114
0
  }
115
116
0
  fprintf(fp, "[bundle]\n");
117
0
  fprintf(fp, "\tversion = %d\n", list->version);
118
0
  fprintf(fp, "\tmode = %s\n", mode);
119
120
0
  if (list->heuristic) {
121
0
    int i;
122
0
    for (i = 0; i < BUNDLE_HEURISTIC__COUNT; i++) {
123
0
      if (heuristics[i].heuristic == list->heuristic) {
124
0
        printf("\theuristic = %s\n",
125
0
               heuristics[list->heuristic].name);
126
0
        break;
127
0
      }
128
0
    }
129
0
  }
130
131
0
  for_all_bundles_in_list(list, summarize_bundle, fp);
132
0
}
133
134
/**
135
 * Given a key-value pair, update the state of the given bundle list.
136
 * Returns 0 if the key-value pair is understood. Returns -1 if the key
137
 * is not understood or the value is malformed.
138
 */
139
static int bundle_list_update(const char *key, const char *value,
140
            struct bundle_list *list)
141
0
{
142
0
  struct strbuf id = STRBUF_INIT;
143
0
  struct remote_bundle_info lookup = REMOTE_BUNDLE_INFO_INIT;
144
0
  struct remote_bundle_info *bundle;
145
0
  const char *subsection, *subkey;
146
0
  size_t subsection_len;
147
148
0
  if (parse_config_key(key, "bundle", &subsection, &subsection_len, &subkey))
149
0
    return -1;
150
151
0
  if (!subsection_len) {
152
0
    if (!strcmp(subkey, "version")) {
153
0
      int version;
154
0
      if (!git_parse_int(value, &version))
155
0
        return -1;
156
0
      if (version != 1)
157
0
        return -1;
158
159
0
      list->version = version;
160
0
      return 0;
161
0
    }
162
163
0
    if (!strcmp(subkey, "mode")) {
164
0
      if (!strcmp(value, "all"))
165
0
        list->mode = BUNDLE_MODE_ALL;
166
0
      else if (!strcmp(value, "any"))
167
0
        list->mode = BUNDLE_MODE_ANY;
168
0
      else
169
0
        return -1;
170
0
      return 0;
171
0
    }
172
173
0
    if (!strcmp(subkey, "heuristic")) {
174
0
      int i;
175
0
      for (i = 0; i < BUNDLE_HEURISTIC__COUNT; i++) {
176
0
        if (heuristics[i].heuristic &&
177
0
            heuristics[i].name &&
178
0
            !strcmp(value, heuristics[i].name)) {
179
0
          list->heuristic = heuristics[i].heuristic;
180
0
          return 0;
181
0
        }
182
0
      }
183
184
      /* Ignore unknown heuristics. */
185
0
      return 0;
186
0
    }
187
188
    /* Ignore other unknown global keys. */
189
0
    return 0;
190
0
  }
191
192
0
  strbuf_add(&id, subsection, subsection_len);
193
194
  /*
195
   * Check for an existing bundle with this <id>, or create one
196
   * if necessary.
197
   */
198
0
  lookup.id = id.buf;
199
0
  hashmap_entry_init(&lookup.ent, strhash(lookup.id));
200
0
  if (!(bundle = hashmap_get_entry(&list->bundles, &lookup, ent, NULL))) {
201
0
    CALLOC_ARRAY(bundle, 1);
202
0
    bundle->id = strbuf_detach(&id, NULL);
203
0
    hashmap_entry_init(&bundle->ent, strhash(bundle->id));
204
0
    hashmap_add(&list->bundles, &bundle->ent);
205
0
  }
206
0
  strbuf_release(&id);
207
208
0
  if (!strcmp(subkey, "uri")) {
209
0
    if (bundle->uri)
210
0
      return -1;
211
0
    bundle->uri = relative_url(list->baseURI, value, NULL);
212
0
    return 0;
213
0
  }
214
215
0
  if (!strcmp(subkey, "creationtoken")) {
216
0
    if (sscanf(value, "%"PRIu64, &bundle->creationToken) != 1)
217
0
      warning(_("could not parse bundle list key %s with value '%s'"),
218
0
        "creationToken", value);
219
0
    return 0;
220
0
  }
221
222
  /*
223
   * At this point, we ignore any information that we don't
224
   * understand, assuming it to be hints for a heuristic the client
225
   * does not currently understand.
226
   */
227
0
  return 0;
228
0
}
229
230
static int config_to_bundle_list(const char *key, const char *value,
231
         const struct config_context *ctx UNUSED,
232
         void *data)
233
0
{
234
0
  struct bundle_list *list = data;
235
0
  return bundle_list_update(key, value, list);
236
0
}
237
238
int bundle_uri_parse_config_format(const char *uri,
239
           const char *filename,
240
           struct bundle_list *list)
241
0
{
242
0
  int result;
243
0
  struct config_options opts = {
244
0
    .error_action = CONFIG_ERROR_ERROR,
245
0
  };
246
247
0
  if (!list->baseURI) {
248
0
    struct strbuf baseURI = STRBUF_INIT;
249
0
    strbuf_addstr(&baseURI, uri);
250
251
    /*
252
     * If the URI does not end with a trailing slash, then
253
     * remove the filename portion of the path. This is
254
     * important for relative URIs.
255
     */
256
0
    strbuf_strip_file_from_path(&baseURI);
257
0
    list->baseURI = strbuf_detach(&baseURI, NULL);
258
0
  }
259
0
  result = git_config_from_file_with_options(config_to_bundle_list,
260
0
               filename, list,
261
0
               CONFIG_SCOPE_UNKNOWN,
262
0
               &opts);
263
264
0
  if (!result && list->mode == BUNDLE_MODE_NONE) {
265
0
    warning(_("bundle list at '%s' has no mode"), uri);
266
0
    result = 1;
267
0
  }
268
269
0
  return result;
270
0
}
271
272
static char *find_temp_filename(void)
273
0
{
274
0
  int fd;
275
0
  struct strbuf name = STRBUF_INIT;
276
  /*
277
   * Find a temporary filename that is available. This is briefly
278
   * racy, but unlikely to collide.
279
   */
280
0
  fd = odb_mkstemp(&name, "bundles/tmp_uri_XXXXXX");
281
0
  if (fd < 0) {
282
0
    warning(_("failed to create temporary file"));
283
0
    return NULL;
284
0
  }
285
286
0
  close(fd);
287
0
  unlink(name.buf);
288
0
  return strbuf_detach(&name, NULL);
289
0
}
290
291
static int download_https_uri_to_file(const char *file, const char *uri)
292
0
{
293
0
  int result = 0;
294
0
  struct child_process cp = CHILD_PROCESS_INIT;
295
0
  FILE *child_in = NULL, *child_out = NULL;
296
0
  struct strbuf line = STRBUF_INIT;
297
0
  int found_get = 0;
298
299
0
  strvec_pushl(&cp.args, "git-remote-https", uri, NULL);
300
0
  cp.err = -1;
301
0
  cp.in = -1;
302
0
  cp.out = -1;
303
304
0
  if (start_command(&cp))
305
0
    return 1;
306
307
0
  child_in = fdopen(cp.in, "w");
308
0
  if (!child_in) {
309
0
    result = 1;
310
0
    goto cleanup;
311
0
  }
312
313
0
  child_out = fdopen(cp.out, "r");
314
0
  if (!child_out) {
315
0
    result = 1;
316
0
    goto cleanup;
317
0
  }
318
319
0
  fprintf(child_in, "capabilities\n");
320
0
  fflush(child_in);
321
322
0
  while (!strbuf_getline(&line, child_out)) {
323
0
    if (!line.len)
324
0
      break;
325
0
    if (!strcmp(line.buf, "get"))
326
0
      found_get = 1;
327
0
  }
328
0
  strbuf_release(&line);
329
330
0
  if (!found_get) {
331
0
    result = error(_("insufficient capabilities"));
332
0
    goto cleanup;
333
0
  }
334
335
0
  fprintf(child_in, "get %s %s\n\n", uri, file);
336
337
0
cleanup:
338
0
  if (child_in)
339
0
    fclose(child_in);
340
0
  if (finish_command(&cp))
341
0
    return 1;
342
0
  if (child_out)
343
0
    fclose(child_out);
344
0
  return result;
345
0
}
346
347
static int copy_uri_to_file(const char *filename, const char *uri)
348
0
{
349
0
  const char *out;
350
351
0
  if (starts_with(uri, "https:") ||
352
0
      starts_with(uri, "http:"))
353
0
    return download_https_uri_to_file(filename, uri);
354
355
0
  if (skip_prefix(uri, "file://", &out))
356
0
    uri = out;
357
358
  /* Copy as a file */
359
0
  return copy_file(filename, uri, 0);
360
0
}
361
362
static int unbundle_from_file(struct repository *r, const char *file)
363
0
{
364
0
  int result = 0;
365
0
  int bundle_fd;
366
0
  struct bundle_header header = BUNDLE_HEADER_INIT;
367
0
  struct string_list_item *refname;
368
0
  struct strbuf bundle_ref = STRBUF_INIT;
369
0
  size_t bundle_prefix_len;
370
371
0
  if ((bundle_fd = read_bundle_header(file, &header)) < 0)
372
0
    return 1;
373
374
  /*
375
   * Skip the reachability walk here, since we will be adding
376
   * a reachable ref pointing to the new tips, which will reach
377
   * the prerequisite commits.
378
   */
379
0
  if ((result = unbundle(r, &header, bundle_fd, NULL,
380
0
             VERIFY_BUNDLE_QUIET | (fetch_pack_fsck_objects() ? VERIFY_BUNDLE_FSCK : 0))))
381
0
    return 1;
382
383
  /*
384
   * Convert all refs/heads/ from the bundle into refs/bundles/
385
   * in the local repository.
386
   */
387
0
  strbuf_addstr(&bundle_ref, "refs/bundles/");
388
0
  bundle_prefix_len = bundle_ref.len;
389
390
0
  for_each_string_list_item(refname, &header.references) {
391
0
    struct object_id *oid = refname->util;
392
0
    struct object_id old_oid;
393
0
    const char *branch_name;
394
0
    int has_old;
395
396
0
    if (!skip_prefix(refname->string, "refs/heads/", &branch_name))
397
0
      continue;
398
399
0
    strbuf_setlen(&bundle_ref, bundle_prefix_len);
400
0
    strbuf_addstr(&bundle_ref, branch_name);
401
402
0
    has_old = !refs_read_ref(get_main_ref_store(the_repository),
403
0
           bundle_ref.buf, &old_oid);
404
0
    refs_update_ref(get_main_ref_store(the_repository),
405
0
        "fetched bundle", bundle_ref.buf, oid,
406
0
        has_old ? &old_oid : NULL,
407
0
        0, UPDATE_REFS_MSG_ON_ERR);
408
0
  }
409
410
0
  bundle_header_release(&header);
411
0
  return result;
412
0
}
413
414
struct bundle_list_context {
415
  struct repository *r;
416
  struct bundle_list *list;
417
  enum bundle_list_mode mode;
418
  int count;
419
  int depth;
420
};
421
422
/*
423
 * This early definition is necessary because we use indirect recursion:
424
 *
425
 * While iterating through a bundle list that was downloaded as part
426
 * of fetch_bundle_uri_internal(), iterator methods eventually call it
427
 * again, but with depth + 1.
428
 */
429
static int fetch_bundle_uri_internal(struct repository *r,
430
             struct remote_bundle_info *bundle,
431
             int depth,
432
             struct bundle_list *list);
433
434
static int download_bundle_to_file(struct remote_bundle_info *bundle, void *data)
435
0
{
436
0
  int res;
437
0
  struct bundle_list_context *ctx = data;
438
439
0
  if (ctx->mode == BUNDLE_MODE_ANY && ctx->count)
440
0
    return 0;
441
442
0
  res = fetch_bundle_uri_internal(ctx->r, bundle, ctx->depth + 1, ctx->list);
443
444
  /*
445
   * Only increment count if the download succeeded. If our mode is
446
   * BUNDLE_MODE_ANY, then we will want to try other URIs in the
447
   * list in case they work instead.
448
   */
449
0
  if (!res)
450
0
    ctx->count++;
451
452
  /*
453
   * To be opportunistic as possible, we continue iterating and
454
   * download as many bundles as we can, so we can apply the ones
455
   * that work, even in BUNDLE_MODE_ALL mode.
456
   */
457
0
  return 0;
458
0
}
459
460
struct bundles_for_sorting {
461
  struct remote_bundle_info **items;
462
  size_t alloc;
463
  size_t nr;
464
};
465
466
static int append_bundle(struct remote_bundle_info *bundle, void *data)
467
0
{
468
0
  struct bundles_for_sorting *list = data;
469
0
  list->items[list->nr++] = bundle;
470
0
  return 0;
471
0
}
472
473
/**
474
 * For use in QSORT() to get a list sorted by creationToken
475
 * in decreasing order.
476
 */
477
static int compare_creation_token_decreasing(const void *va, const void *vb)
478
0
{
479
0
  const struct remote_bundle_info * const *a = va;
480
0
  const struct remote_bundle_info * const *b = vb;
481
482
0
  if ((*a)->creationToken > (*b)->creationToken)
483
0
    return -1;
484
0
  if ((*a)->creationToken < (*b)->creationToken)
485
0
    return 1;
486
0
  return 0;
487
0
}
488
489
static int fetch_bundles_by_token(struct repository *r,
490
          struct bundle_list *list)
491
0
{
492
0
  int cur;
493
0
  int move_direction = 0;
494
0
  const char *creationTokenStr;
495
0
  uint64_t maxCreationToken = 0, newMaxCreationToken = 0;
496
0
  struct bundle_list_context ctx = {
497
0
    .r = r,
498
0
    .list = list,
499
0
    .mode = list->mode,
500
0
  };
501
0
  struct bundles_for_sorting bundles = {
502
0
    .alloc = hashmap_get_size(&list->bundles),
503
0
  };
504
505
0
  ALLOC_ARRAY(bundles.items, bundles.alloc);
506
507
0
  for_all_bundles_in_list(list, append_bundle, &bundles);
508
509
0
  if (!bundles.nr) {
510
0
    free(bundles.items);
511
0
    return 0;
512
0
  }
513
514
0
  QSORT(bundles.items, bundles.nr, compare_creation_token_decreasing);
515
516
  /*
517
   * If fetch.bundleCreationToken exists, parses to a uint64t, and
518
   * is not strictly smaller than the maximum creation token in the
519
   * bundle list, then do not download any bundles.
520
   */
521
0
  if (!repo_config_get_value(r,
522
0
           "fetch.bundlecreationtoken",
523
0
           &creationTokenStr) &&
524
0
      sscanf(creationTokenStr, "%"PRIu64, &maxCreationToken) == 1 &&
525
0
      bundles.items[0]->creationToken <= maxCreationToken) {
526
0
    free(bundles.items);
527
0
    return 0;
528
0
  }
529
530
  /*
531
   * Attempt to download and unbundle the minimum number of bundles by
532
   * creationToken in decreasing order. If we fail to unbundle (after
533
   * a successful download) then move to the next non-downloaded bundle
534
   * and attempt downloading. Once we succeed in applying a bundle,
535
   * move to the previous unapplied bundle and attempt to unbundle it
536
   * again.
537
   *
538
   * In the case of a fresh clone, we will likely download all of the
539
   * bundles before successfully unbundling the oldest one, then the
540
   * rest of the bundles unbundle successfully in increasing order
541
   * of creationToken.
542
   *
543
   * If there are existing objects, then this process may terminate
544
   * early when all required commits from "new" bundles exist in the
545
   * repo's object store.
546
   */
547
0
  cur = 0;
548
0
  while (cur >= 0 && cur < bundles.nr) {
549
0
    struct remote_bundle_info *bundle = bundles.items[cur];
550
551
    /*
552
     * If we need to dig into bundles below the previous
553
     * creation token value, then likely we are in an erroneous
554
     * state due to missing or invalid bundles. Halt the process
555
     * instead of continuing to download extra data.
556
     */
557
0
    if (bundle->creationToken <= maxCreationToken)
558
0
      break;
559
560
0
    if (!bundle->file) {
561
      /*
562
       * Not downloaded yet. Try downloading.
563
       *
564
       * Note that bundle->file is non-NULL if a download
565
       * was attempted, even if it failed to download.
566
       */
567
0
      if (fetch_bundle_uri_internal(ctx.r, bundle, ctx.depth + 1, ctx.list)) {
568
        /* Mark as unbundled so we do not retry. */
569
0
        bundle->unbundled = 1;
570
571
        /* Try looking deeper in the list. */
572
0
        move_direction = 1;
573
0
        goto move;
574
0
      }
575
576
      /* We expect bundles when using creationTokens. */
577
0
      if (!is_bundle(bundle->file, 1)) {
578
0
        warning(_("file downloaded from '%s' is not a bundle"),
579
0
          bundle->uri);
580
0
        break;
581
0
      }
582
0
    }
583
584
0
    if (bundle->file && !bundle->unbundled) {
585
      /*
586
       * This was downloaded, but not successfully
587
       * unbundled. Try unbundling again.
588
       */
589
0
      if (unbundle_from_file(ctx.r, bundle->file)) {
590
        /* Try looking deeper in the list. */
591
0
        move_direction = 1;
592
0
      } else {
593
        /*
594
         * Succeeded in unbundle. Retry bundles
595
         * that previously failed to unbundle.
596
         */
597
0
        move_direction = -1;
598
0
        bundle->unbundled = 1;
599
600
0
        if (bundle->creationToken > newMaxCreationToken)
601
0
          newMaxCreationToken = bundle->creationToken;
602
0
      }
603
0
    }
604
605
    /*
606
     * Else case: downloaded and unbundled successfully.
607
     * Skip this by moving in the same direction as the
608
     * previous step.
609
     */
610
611
0
move:
612
    /* Move in the specified direction and repeat. */
613
0
    cur += move_direction;
614
0
  }
615
616
  /*
617
   * We succeed if the loop terminates because 'cur' drops below
618
   * zero. The other case is that we terminate because 'cur'
619
   * reaches the end of the list, so we have a failure no matter
620
   * which bundles we apply from the list.
621
   */
622
0
  if (cur < 0) {
623
0
    struct strbuf value = STRBUF_INIT;
624
0
    strbuf_addf(&value, "%"PRIu64"", newMaxCreationToken);
625
0
    if (repo_config_set_multivar_gently(ctx.r,
626
0
                "fetch.bundleCreationToken",
627
0
                value.buf, NULL, 0))
628
0
      warning(_("failed to store maximum creation token"));
629
630
0
    strbuf_release(&value);
631
0
  }
632
633
0
  free(bundles.items);
634
0
  return cur >= 0;
635
0
}
636
637
static int download_bundle_list(struct repository *r,
638
        struct bundle_list *local_list,
639
        struct bundle_list *global_list,
640
        int depth)
641
0
{
642
0
  struct bundle_list_context ctx = {
643
0
    .r = r,
644
0
    .list = global_list,
645
0
    .depth = depth + 1,
646
0
    .mode = local_list->mode,
647
0
  };
648
649
0
  return for_all_bundles_in_list(local_list, download_bundle_to_file, &ctx);
650
0
}
651
652
static int fetch_bundle_list_in_config_format(struct repository *r,
653
                struct bundle_list *global_list,
654
                struct remote_bundle_info *bundle,
655
                int depth)
656
0
{
657
0
  int result;
658
0
  struct bundle_list list_from_bundle;
659
660
0
  init_bundle_list(&list_from_bundle);
661
662
0
  if ((result = bundle_uri_parse_config_format(bundle->uri,
663
0
                 bundle->file,
664
0
                 &list_from_bundle)))
665
0
    goto cleanup;
666
667
0
  if (list_from_bundle.mode == BUNDLE_MODE_NONE) {
668
0
    warning(_("unrecognized bundle mode from URI '%s'"),
669
0
      bundle->uri);
670
0
    result = -1;
671
0
    goto cleanup;
672
0
  }
673
674
  /*
675
   * If this list uses the creationToken heuristic, then the URIs
676
   * it advertises are expected to be bundles, not nested lists.
677
   * We can drop 'global_list' and 'depth'.
678
   */
679
0
  if (list_from_bundle.heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN) {
680
0
    result = fetch_bundles_by_token(r, &list_from_bundle);
681
0
    global_list->heuristic = BUNDLE_HEURISTIC_CREATIONTOKEN;
682
0
  } else if ((result = download_bundle_list(r, &list_from_bundle,
683
0
             global_list, depth)))
684
0
    goto cleanup;
685
686
0
cleanup:
687
0
  clear_bundle_list(&list_from_bundle);
688
0
  return result;
689
0
}
690
691
/**
692
 * This limits the recursion on fetch_bundle_uri_internal() when following
693
 * bundle lists.
694
 */
695
static int max_bundle_uri_depth = 4;
696
697
/**
698
 * Recursively download all bundles advertised at the given URI
699
 * to files. If the file is a bundle, then add it to the given
700
 * 'list'. Otherwise, expect a bundle list and recurse on the
701
 * URIs in that list according to the list mode (ANY or ALL).
702
 */
703
static int fetch_bundle_uri_internal(struct repository *r,
704
             struct remote_bundle_info *bundle,
705
             int depth,
706
             struct bundle_list *list)
707
0
{
708
0
  int result = 0;
709
0
  struct remote_bundle_info *bcopy;
710
711
0
  if (depth >= max_bundle_uri_depth) {
712
0
    warning(_("exceeded bundle URI recursion limit (%d)"),
713
0
      max_bundle_uri_depth);
714
0
    return -1;
715
0
  }
716
717
0
  if (!bundle->file &&
718
0
      !(bundle->file = find_temp_filename())) {
719
0
    result = -1;
720
0
    goto cleanup;
721
0
  }
722
723
0
  if ((result = copy_uri_to_file(bundle->file, bundle->uri))) {
724
0
    warning(_("failed to download bundle from URI '%s'"), bundle->uri);
725
0
    goto cleanup;
726
0
  }
727
728
0
  if ((result = !is_bundle(bundle->file, 1))) {
729
0
    result = fetch_bundle_list_in_config_format(
730
0
        r, list, bundle, depth);
731
0
    if (result)
732
0
      warning(_("file at URI '%s' is not a bundle or bundle list"),
733
0
        bundle->uri);
734
0
    goto cleanup;
735
0
  }
736
737
  /* Copy the bundle and insert it into the global list. */
738
0
  CALLOC_ARRAY(bcopy, 1);
739
0
  bcopy->id = xstrdup(bundle->id);
740
0
  bcopy->file = xstrdup(bundle->file);
741
0
  hashmap_entry_init(&bcopy->ent, strhash(bcopy->id));
742
0
  hashmap_add(&list->bundles, &bcopy->ent);
743
744
0
cleanup:
745
0
  if (result && bundle->file)
746
0
    unlink(bundle->file);
747
0
  return result;
748
0
}
749
750
/**
751
 * This loop iterator breaks the loop with nonzero return code on the
752
 * first successful unbundling of a bundle.
753
 */
754
static int attempt_unbundle(struct remote_bundle_info *info, void *data)
755
0
{
756
0
  struct repository *r = data;
757
758
0
  if (!info->file || info->unbundled)
759
0
    return 0;
760
761
0
  if (!unbundle_from_file(r, info->file)) {
762
0
    info->unbundled = 1;
763
0
    return 1;
764
0
  }
765
766
0
  return 0;
767
0
}
768
769
static int unbundle_all_bundles(struct repository *r,
770
        struct bundle_list *list)
771
0
{
772
  /*
773
   * Iterate through all bundles looking for ones that can
774
   * successfully unbundle. If any succeed, then perhaps another
775
   * will succeed in the next attempt.
776
   *
777
   * Keep in mind that a non-zero result for the loop here means
778
   * the loop terminated early on a successful unbundling, which
779
   * signals that we can try again.
780
   */
781
0
  while (for_all_bundles_in_list(list, attempt_unbundle, r)) ;
782
783
0
  return 0;
784
0
}
785
786
static int unlink_bundle(struct remote_bundle_info *info, void *data UNUSED)
787
0
{
788
0
  if (info->file)
789
0
    unlink_or_warn(info->file);
790
0
  return 0;
791
0
}
792
793
int fetch_bundle_uri(struct repository *r, const char *uri,
794
         int *has_heuristic)
795
0
{
796
0
  int result;
797
0
  struct bundle_list list;
798
0
  struct remote_bundle_info bundle = {
799
0
    .uri = xstrdup(uri),
800
0
    .id = xstrdup(""),
801
0
  };
802
803
0
  trace2_region_enter("fetch", "fetch-bundle-uri", the_repository);
804
805
0
  init_bundle_list(&list);
806
807
  /*
808
   * Do not fetch an empty bundle URI. An empty bundle URI
809
   * could signal that a configured bundle URI has been disabled.
810
   */
811
0
  if (!*uri) {
812
0
    result = 0;
813
0
    goto cleanup;
814
0
  }
815
816
  /* If a bundle is added to this global list, then it is required. */
817
0
  list.mode = BUNDLE_MODE_ALL;
818
819
0
  if ((result = fetch_bundle_uri_internal(r, &bundle, 0, &list)))
820
0
    goto cleanup;
821
822
0
  result = unbundle_all_bundles(r, &list);
823
824
0
cleanup:
825
0
  if (has_heuristic)
826
0
    *has_heuristic = (list.heuristic != BUNDLE_HEURISTIC_NONE);
827
0
  for_all_bundles_in_list(&list, unlink_bundle, NULL);
828
0
  clear_bundle_list(&list);
829
0
  clear_remote_bundle_info(&bundle, NULL);
830
0
  trace2_region_leave("fetch", "fetch-bundle-uri", the_repository);
831
0
  return result;
832
0
}
833
834
int fetch_bundle_list(struct repository *r, struct bundle_list *list)
835
0
{
836
0
  int result;
837
0
  struct bundle_list global_list;
838
839
  /*
840
   * If the creationToken heuristic is used, then the URIs
841
   * advertised by 'list' are not nested lists and instead
842
   * direct bundles. We do not need to use global_list.
843
   */
844
0
  if (list->heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN)
845
0
    return fetch_bundles_by_token(r, list);
846
847
0
  init_bundle_list(&global_list);
848
849
  /* If a bundle is added to this global list, then it is required. */
850
0
  global_list.mode = BUNDLE_MODE_ALL;
851
852
0
  if ((result = download_bundle_list(r, list, &global_list, 0)))
853
0
    goto cleanup;
854
855
0
  if (list->heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN)
856
0
    result = fetch_bundles_by_token(r, list);
857
0
  else
858
0
    result = unbundle_all_bundles(r, &global_list);
859
860
0
cleanup:
861
0
  for_all_bundles_in_list(&global_list, unlink_bundle, NULL);
862
0
  clear_bundle_list(&global_list);
863
0
  return result;
864
0
}
865
866
/**
867
 * API for serve.c.
868
 */
869
870
int bundle_uri_advertise(struct repository *r, struct strbuf *value UNUSED)
871
0
{
872
0
  static int advertise_bundle_uri = -1;
873
874
0
  if (advertise_bundle_uri != -1)
875
0
    goto cached;
876
877
0
  advertise_bundle_uri = 0;
878
0
  repo_config_get_maybe_bool(r, "uploadpack.advertisebundleuris", &advertise_bundle_uri);
879
880
0
cached:
881
0
  return advertise_bundle_uri;
882
0
}
883
884
static int config_to_packet_line(const char *key, const char *value,
885
         const struct config_context *ctx UNUSED,
886
         void *data)
887
0
{
888
0
  struct packet_reader *writer = data;
889
890
0
  if (starts_with(key, "bundle."))
891
0
    packet_write_fmt(writer->fd, "%s=%s", key, value);
892
893
0
  return 0;
894
0
}
895
896
int bundle_uri_command(struct repository *r,
897
           struct packet_reader *request)
898
0
{
899
0
  struct packet_writer writer;
900
0
  packet_writer_init(&writer, 1);
901
902
0
  while (packet_reader_read(request) == PACKET_READ_NORMAL)
903
0
    die(_("bundle-uri: unexpected argument: '%s'"), request->line);
904
0
  if (request->status != PACKET_READ_FLUSH)
905
0
    die(_("bundle-uri: expected flush after arguments"));
906
907
  /*
908
   * Read all "bundle.*" config lines to the client as key=value
909
   * packet lines.
910
   */
911
0
  repo_config(r, config_to_packet_line, &writer);
912
913
0
  packet_writer_flush(&writer);
914
915
0
  return 0;
916
0
}
917
918
/**
919
 * General API for {transport,connect}.c etc.
920
 */
921
int bundle_uri_parse_line(struct bundle_list *list, const char *line)
922
0
{
923
0
  int result;
924
0
  const char *equals;
925
0
  struct strbuf key = STRBUF_INIT;
926
927
0
  if (!strlen(line))
928
0
    return error(_("bundle-uri: got an empty line"));
929
930
0
  equals = strchr(line, '=');
931
932
0
  if (!equals)
933
0
    return error(_("bundle-uri: line is not of the form 'key=value'"));
934
0
  if (line == equals || !*(equals + 1))
935
0
    return error(_("bundle-uri: line has empty key or value"));
936
937
0
  strbuf_add(&key, line, equals - line);
938
0
  result = bundle_list_update(key.buf, equals + 1, list);
939
0
  strbuf_release(&key);
940
941
0
  return result;
942
0
}