Coverage Report

Created: 2026-10-17 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/promisor-remote.c
Line
Count
Source
1
#define USE_THE_REPOSITORY_VARIABLE
2
3
#include "git-compat-util.h"
4
#include "gettext.h"
5
#include "hex.h"
6
#include "odb.h"
7
#include "promisor-remote.h"
8
#include "config.h"
9
#include "trace2.h"
10
#include "transport.h"
11
#include "strvec.h"
12
#include "packfile.h"
13
#include "environment.h"
14
#include "url.h"
15
#include "urlmatch.h"
16
#include "version.h"
17
#include "wildmatch.h"
18
19
struct promisor_remote_config {
20
  struct promisor_remote *promisors;
21
  struct promisor_remote **promisors_tail;
22
};
23
24
static int fetch_objects(struct repository *repo,
25
       const char *remote_name,
26
       const struct object_id *oids,
27
       int oid_nr)
28
0
{
29
0
  struct child_process child = CHILD_PROCESS_INIT;
30
0
  int i;
31
0
  FILE *child_in;
32
0
  int quiet;
33
34
0
  if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) {
35
0
    static int warning_shown;
36
0
    if (!warning_shown) {
37
0
      warning_shown = 1;
38
0
      warning(_("lazy fetching disabled; some objects may not be available"));
39
0
    }
40
0
    return -1;
41
0
  }
42
43
0
  child.git_cmd = 1;
44
0
  child.in = -1;
45
0
  if (repo != the_repository)
46
0
    prepare_other_repo_env(&child.env, repo->gitdir);
47
0
  strvec_pushl(&child.args, "-c", "fetch.negotiationAlgorithm=noop",
48
0
         "fetch", remote_name, "--no-tags",
49
0
         "--no-write-fetch-head", "--recurse-submodules=no",
50
0
         "--filter=blob:none", "--stdin", NULL);
51
0
  if (!repo_config_get_bool(repo, "promisor.quiet", &quiet) && quiet)
52
0
    strvec_push(&child.args, "--quiet");
53
0
  if (start_command(&child))
54
0
    die(_("promisor-remote: unable to fork off fetch subprocess"));
55
0
  child_in = xfdopen(child.in, "w");
56
57
0
  trace2_data_intmax("promisor", repo, "fetch_count", oid_nr);
58
59
0
  for (i = 0; i < oid_nr; i++) {
60
0
    if (fputs(oid_to_hex(&oids[i]), child_in) < 0)
61
0
      die_errno(_("promisor-remote: could not write to fetch subprocess"));
62
0
    if (fputc('\n', child_in) < 0)
63
0
      die_errno(_("promisor-remote: could not write to fetch subprocess"));
64
0
  }
65
66
0
  if (fclose(child_in) < 0)
67
0
    die_errno(_("promisor-remote: could not close stdin to fetch subprocess"));
68
0
  return finish_command(&child) ? -1 : 0;
69
0
}
70
71
static struct promisor_remote *promisor_remote_new(struct promisor_remote_config *config,
72
               const char *remote_name)
73
0
{
74
0
  struct promisor_remote *r;
75
76
0
  if (*remote_name == '/') {
77
0
    warning(_("promisor remote name cannot begin with '/': %s"),
78
0
      remote_name);
79
0
    return NULL;
80
0
  }
81
82
0
  FLEX_ALLOC_STR(r, name, remote_name);
83
84
0
  *config->promisors_tail = r;
85
0
  config->promisors_tail = &r->next;
86
87
0
  return r;
88
0
}
89
90
static struct promisor_remote *promisor_remote_lookup(struct promisor_remote_config *config,
91
                  const char *remote_name,
92
                  struct promisor_remote **previous)
93
0
{
94
0
  struct promisor_remote *r, *p;
95
96
0
  for (p = NULL, r = config->promisors; r; p = r, r = r->next)
97
0
    if (!strcmp(r->name, remote_name)) {
98
0
      if (previous)
99
0
        *previous = p;
100
0
      return r;
101
0
    }
102
103
0
  return NULL;
104
0
}
105
106
static void promisor_remote_move_to_tail(struct promisor_remote_config *config,
107
           struct promisor_remote *r,
108
           struct promisor_remote *previous)
109
0
{
110
0
  if (!r->next)
111
0
    return;
112
113
0
  if (previous)
114
0
    previous->next = r->next;
115
0
  else
116
0
    config->promisors = r->next ? r->next : r;
117
0
  r->next = NULL;
118
0
  *config->promisors_tail = r;
119
0
  config->promisors_tail = &r->next;
120
0
}
121
122
static int promisor_remote_config(const char *var, const char *value,
123
          const struct config_context *ctx UNUSED,
124
          void *data)
125
0
{
126
0
  struct promisor_remote_config *config = data;
127
0
  const char *name;
128
0
  size_t namelen;
129
0
  const char *subkey;
130
131
0
  if (parse_config_key(var, "remote", &name, &namelen, &subkey) < 0)
132
0
    return 0;
133
134
0
  if (!strcmp(subkey, "promisor")) {
135
0
    char *remote_name;
136
137
0
    if (!git_config_bool(var, value))
138
0
      return 0;
139
140
0
    remote_name = xmemdupz(name, namelen);
141
142
0
    if (!promisor_remote_lookup(config, remote_name, NULL))
143
0
      promisor_remote_new(config, remote_name);
144
145
0
    free(remote_name);
146
0
    return 0;
147
0
  }
148
0
  if (!strcmp(subkey, "partialclonefilter")) {
149
0
    struct promisor_remote *r;
150
0
    char *remote_name = xmemdupz(name, namelen);
151
152
0
    r = promisor_remote_lookup(config, remote_name, NULL);
153
0
    if (!r)
154
0
      r = promisor_remote_new(config, remote_name);
155
156
0
    free(remote_name);
157
158
0
    if (!r)
159
0
      return 0;
160
161
0
    FREE_AND_NULL(r->partial_clone_filter);
162
0
    return git_config_string(&r->partial_clone_filter, var, value);
163
0
  }
164
165
0
  return 0;
166
0
}
167
168
static void promisor_remote_init(struct repository *r)
169
0
{
170
0
  struct promisor_remote_config *config;
171
172
0
  if (r->promisor_remote_config)
173
0
    return;
174
0
  config = r->promisor_remote_config =
175
0
    xcalloc(1, sizeof(*r->promisor_remote_config));
176
0
  config->promisors_tail = &config->promisors;
177
178
0
  repo_config(r, promisor_remote_config, config);
179
180
0
  if (r->repository_format_partial_clone) {
181
0
    struct promisor_remote *o, *previous;
182
183
0
    o = promisor_remote_lookup(config,
184
0
             r->repository_format_partial_clone,
185
0
             &previous);
186
0
    if (o)
187
0
      promisor_remote_move_to_tail(config, o, previous);
188
0
    else
189
0
      promisor_remote_new(config, r->repository_format_partial_clone);
190
0
  }
191
0
}
192
193
void promisor_remote_clear(struct promisor_remote_config *config)
194
0
{
195
0
  while (config->promisors) {
196
0
    struct promisor_remote *r = config->promisors;
197
0
    free(r->partial_clone_filter);
198
0
    free(r->advertised_filter);
199
0
    config->promisors = config->promisors->next;
200
0
    free(r);
201
0
  }
202
203
0
  config->promisors_tail = &config->promisors;
204
0
}
205
206
void repo_promisor_remote_reinit(struct repository *r)
207
0
{
208
0
  promisor_remote_clear(r->promisor_remote_config);
209
0
  FREE_AND_NULL(r->promisor_remote_config);
210
0
  promisor_remote_init(r);
211
0
}
212
213
struct promisor_remote *repo_promisor_remote_find(struct repository *r,
214
              const char *remote_name)
215
0
{
216
0
  promisor_remote_init(r);
217
218
0
  if (!remote_name)
219
0
    return r->promisor_remote_config->promisors;
220
221
0
  return promisor_remote_lookup(r->promisor_remote_config, remote_name, NULL);
222
0
}
223
224
int repo_has_promisor_remote(struct repository *r)
225
0
{
226
0
  return !!repo_promisor_remote_find(r, NULL);
227
0
}
228
229
int repo_has_accepted_promisor_remote(struct repository *r)
230
0
{
231
0
  struct promisor_remote *p;
232
233
0
  promisor_remote_init(r);
234
235
0
  for (p = r->promisor_remote_config->promisors; p; p = p->next)
236
0
    if (p->accepted)
237
0
      return 1;
238
0
  return 0;
239
0
}
240
241
static int remove_fetched_oids(struct repository *repo,
242
             struct object_id **oids,
243
             int oid_nr, int to_free)
244
0
{
245
0
  int i, remaining_nr = 0;
246
0
  int *remaining = xcalloc(oid_nr, sizeof(*remaining));
247
0
  struct object_id *old_oids = *oids;
248
0
  struct object_id *new_oids;
249
250
0
  for (i = 0; i < oid_nr; i++)
251
0
    if (odb_read_object_info_extended(repo->objects, &old_oids[i], NULL,
252
0
              OBJECT_INFO_SKIP_FETCH_OBJECT)) {
253
0
      remaining[i] = 1;
254
0
      remaining_nr++;
255
0
    }
256
257
0
  if (remaining_nr) {
258
0
    int j = 0;
259
0
    CALLOC_ARRAY(new_oids, remaining_nr);
260
0
    for (i = 0; i < oid_nr; i++)
261
0
      if (remaining[i])
262
0
        oidcpy(&new_oids[j++], &old_oids[i]);
263
0
    *oids = new_oids;
264
0
    if (to_free)
265
0
      free(old_oids);
266
0
  }
267
268
0
  free(remaining);
269
270
0
  return remaining_nr;
271
0
}
272
273
static int try_promisor_remotes(struct repository *repo,
274
        struct object_id **remaining_oids,
275
        int *remaining_nr, int *to_free,
276
        bool accepted_only)
277
0
{
278
0
  struct promisor_remote *r = repo->promisor_remote_config->promisors;
279
280
0
  for (; r; r = r->next) {
281
0
    if (accepted_only != r->accepted)
282
0
      continue;
283
0
    if (fetch_objects(repo, r->name, *remaining_oids, *remaining_nr) < 0) {
284
0
      if (*remaining_nr == 1)
285
0
        continue;
286
0
      *remaining_nr = remove_fetched_oids(repo, remaining_oids,
287
0
                  *remaining_nr, *to_free);
288
0
      if (*remaining_nr) {
289
0
        *to_free = 1;
290
0
        continue;
291
0
      }
292
0
    }
293
0
    return 1; /* all fetched */
294
0
  }
295
0
  return 0;
296
0
}
297
298
void promisor_remote_get_direct(struct repository *repo,
299
        const struct object_id *oids,
300
        int oid_nr)
301
0
{
302
0
  struct object_id *remaining_oids = (struct object_id *)oids;
303
0
  int remaining_nr = oid_nr;
304
0
  int to_free = 0;
305
0
  int i;
306
307
0
  if (oid_nr == 0)
308
0
    return;
309
310
0
  promisor_remote_init(repo);
311
312
  /* Try accepted remotes first (those the server told us to use) */
313
0
  if (try_promisor_remotes(repo, &remaining_oids, &remaining_nr,
314
0
         &to_free, true))
315
0
    goto all_fetched;
316
0
  if (try_promisor_remotes(repo, &remaining_oids, &remaining_nr,
317
0
         &to_free, false))
318
0
    goto all_fetched;
319
320
0
  for (i = 0; i < remaining_nr; i++) {
321
0
    if (is_promisor_object(repo, &remaining_oids[i]))
322
0
      die(_("could not fetch %s from promisor remote"),
323
0
          oid_to_hex(&remaining_oids[i]));
324
0
  }
325
326
0
all_fetched:
327
0
  if (to_free)
328
0
    free(remaining_oids);
329
0
}
330
331
static int allow_unsanitized(char ch)
332
0
{
333
0
  if (ch == ',' || ch == ';' || ch == '%')
334
0
    return 0;
335
0
  return ch > 32 && ch < 127;
336
0
}
337
338
/*
339
 * All the fields used in "promisor-remote" protocol capability,
340
 * including the mandatory "name" and "url" ones.
341
 */
342
static const char promisor_field_name[] = "name";
343
static const char promisor_field_url[] = "url";
344
static const char promisor_field_filter[] = "partialCloneFilter";
345
static const char promisor_field_token[] = "token";
346
347
/*
348
 * List of optional field names that can be used in the
349
 * "promisor-remote" protocol capability (others must be
350
 * ignored). Each field should correspond to a configurable property
351
 * of a remote that can be relevant for the client.
352
 */
353
static const char *known_fields[] = {
354
  promisor_field_filter, /* Filter used for partial clone */
355
  promisor_field_token,  /* Authentication token for the remote */
356
  NULL
357
};
358
359
/*
360
 * Check if 'field' is in the list of the known field names for the
361
 * "promisor-remote" protocol capability.
362
 */
363
static int is_known_field(const char *field)
364
0
{
365
0
  const char **p;
366
367
0
  for (p = known_fields; *p; p++)
368
0
    if (!strcasecmp(*p, field))
369
0
      return 1;
370
0
  return 0;
371
0
}
372
373
static int is_valid_field(struct string_list_item *item, void *cb_data)
374
0
{
375
0
  const char *field = item->string;
376
0
  const char *config_key = (const char *)cb_data;
377
378
0
  if (!is_known_field(field)) {
379
0
    warning(_("unsupported field '%s' in '%s' config"), field, config_key);
380
0
    return 0;
381
0
  }
382
0
  return 1;
383
0
}
384
385
static char *fields_from_config(struct string_list *fields_list, const char *config_key)
386
0
{
387
0
  char *fields = NULL;
388
389
0
  if (!repo_config_get_string(the_repository, config_key, &fields) && *fields) {
390
0
    string_list_split_in_place_f(fields_list, fields, ",", -1,
391
0
               STRING_LIST_SPLIT_TRIM |
392
0
               STRING_LIST_SPLIT_NONEMPTY);
393
0
    filter_string_list(fields_list, 0, is_valid_field, (void *)config_key);
394
0
  }
395
396
0
  return fields;
397
0
}
398
399
static struct string_list *initialize_fields_list(struct string_list *fields_list, int *initialized,
400
              const char *config_key)
401
0
{
402
0
  if (!*initialized) {
403
0
    fields_list->cmp = strcasecmp;
404
0
    fields_from_config(fields_list, config_key);
405
0
    *initialized = 1;
406
0
  }
407
408
0
  return fields_list;
409
0
}
410
411
static struct string_list *fields_sent(void)
412
0
{
413
0
  static struct string_list fields_list = STRING_LIST_INIT_NODUP;
414
0
  static int initialized;
415
416
0
  return initialize_fields_list(&fields_list, &initialized, "promisor.sendFields");
417
0
}
418
419
static struct string_list *fields_checked(void)
420
0
{
421
0
  static struct string_list fields_list = STRING_LIST_INIT_NODUP;
422
0
  static int initialized;
423
424
0
  return initialize_fields_list(&fields_list, &initialized, "promisor.checkFields");
425
0
}
426
427
static struct string_list *fields_stored(void)
428
0
{
429
0
  static struct string_list fields_list = STRING_LIST_INIT_NODUP;
430
0
  static int initialized;
431
432
0
  return initialize_fields_list(&fields_list, &initialized, "promisor.storeFields");
433
0
}
434
435
/*
436
 * Struct for promisor remotes involved in the "promisor-remote"
437
 * protocol capability.
438
 *
439
 * Except for "name" and "local_name", each <member> in this struct
440
 * and its <value> should correspond (either on the client side or on
441
 * the server side) to a "remote.<name>.<member>" config variable set
442
 * to <value> where "<name>" is a promisor remote name.
443
 */
444
struct promisor_info {
445
  const char *name; /* name the server advertised */
446
  const char *local_name; /* name used locally (may be auto-generated) */
447
  const char *url;
448
  const char *filter;
449
  const char *token;
450
};
451
452
static void promisor_info_free(struct promisor_info *p)
453
0
{
454
0
  free((char *)p->name);
455
0
  free((char *)p->local_name);
456
0
  free((char *)p->url);
457
0
  free((char *)p->filter);
458
0
  free((char *)p->token);
459
0
  free(p);
460
0
}
461
462
static void promisor_info_list_clear(struct string_list *list)
463
0
{
464
0
  for (size_t i = 0; i < list->nr; i++)
465
0
    promisor_info_free(list->items[i].util);
466
0
  string_list_clear(list, 0);
467
0
}
468
469
static const char *promisor_info_local_name(struct promisor_info *p)
470
0
{
471
0
  return p->local_name ? p->local_name : p->name;
472
0
}
473
474
static void set_one_field(struct promisor_info *p,
475
        const char *field, const char *value)
476
0
{
477
0
  if (!strcasecmp(field, promisor_field_filter))
478
0
    p->filter = xstrdup(value);
479
0
  else if (!strcasecmp(field, promisor_field_token))
480
0
    p->token = xstrdup(value);
481
0
  else
482
0
    BUG("invalid field '%s'", field);
483
0
}
484
485
static void set_fields(struct promisor_info *p,
486
           struct string_list *field_names)
487
0
{
488
0
  struct string_list_item *item;
489
490
0
  for_each_string_list_item(item, field_names) {
491
0
    char *key = xstrfmt("remote.%s.%s", p->name, item->string);
492
0
    const char *val;
493
0
    if (!repo_config_get_string_tmp(the_repository, key, &val) && *val)
494
0
      set_one_field(p, item->string, val);
495
0
    free(key);
496
0
  }
497
0
}
498
499
/*
500
 * Populate 'list' with promisor remote information from the config.
501
 * The 'util' pointer of each list item will hold a 'struct
502
 * promisor_info'. Except "name" and "url", only members of that
503
 * struct specified by the 'field_names' list are set (using values
504
 * from the configuration).
505
 */
506
static void promisor_config_info_list(struct repository *repo,
507
              struct string_list *list,
508
              struct string_list *field_names)
509
0
{
510
0
  struct promisor_remote *r;
511
512
0
  promisor_remote_init(repo);
513
514
0
  for (r = repo->promisor_remote_config->promisors; r; r = r->next) {
515
0
    const char *url;
516
0
    char *url_key = xstrfmt("remote.%s.url", r->name);
517
518
    /* Only add remotes with a non empty URL */
519
0
    if (!repo_config_get_string_tmp(the_repository, url_key, &url) && *url) {
520
0
      struct promisor_info *new_info = xcalloc(1, sizeof(*new_info));
521
0
      struct string_list_item *item;
522
523
0
      new_info->name = xstrdup(r->name);
524
0
      new_info->url = xstrdup(url);
525
526
0
      if (field_names)
527
0
        set_fields(new_info, field_names);
528
529
0
      item = string_list_append(list, new_info->name);
530
0
      item->util = new_info;
531
0
    }
532
533
0
    free(url_key);
534
0
  }
535
0
}
536
537
char *promisor_remote_info(struct repository *repo)
538
0
{
539
0
  struct strbuf sb = STRBUF_INIT;
540
0
  int advertise_promisors = 0;
541
0
  struct string_list config_info = STRING_LIST_INIT_NODUP;
542
0
  struct string_list_item *item;
543
544
0
  repo_config_get_bool(the_repository, "promisor.advertise", &advertise_promisors);
545
546
0
  if (!advertise_promisors)
547
0
    return NULL;
548
549
0
  promisor_config_info_list(repo, &config_info, fields_sent());
550
551
0
  if (!config_info.nr)
552
0
    return NULL;
553
554
0
  for_each_string_list_item(item, &config_info) {
555
0
    struct promisor_info *p = item->util;
556
557
0
    if (item != config_info.items)
558
0
      strbuf_addch(&sb, ';');
559
560
0
    strbuf_addf(&sb, "%s=", promisor_field_name);
561
0
    strbuf_addstr_urlencode(&sb, p->name, allow_unsanitized);
562
0
    strbuf_addf(&sb, ",%s=", promisor_field_url);
563
0
    strbuf_addstr_urlencode(&sb, p->url, allow_unsanitized);
564
565
0
    if (p->filter) {
566
0
      strbuf_addf(&sb, ",%s=", promisor_field_filter);
567
0
      strbuf_addstr_urlencode(&sb, p->filter, allow_unsanitized);
568
0
    }
569
0
    if (p->token) {
570
0
      strbuf_addf(&sb, ",%s=", promisor_field_token);
571
0
      strbuf_addstr_urlencode(&sb, p->token, allow_unsanitized);
572
0
    }
573
0
  }
574
575
0
  promisor_info_list_clear(&config_info);
576
577
0
  return strbuf_detach(&sb, NULL);
578
0
}
579
580
enum accept_promisor {
581
  ACCEPT_NONE = 0,
582
  ACCEPT_KNOWN_URL,
583
  ACCEPT_KNOWN_NAME,
584
  ACCEPT_ALL
585
};
586
587
/*
588
 * Check if a specific field and its advertised value match the local
589
 * configuration of a given promisor remote.
590
 *
591
 * Returns 1 if they match, 0 otherwise.
592
 */
593
static int match_field_against_config(const char *field, const char *value,
594
              struct promisor_info *config_info)
595
0
{
596
0
  if (config_info->filter && !strcasecmp(field, promisor_field_filter))
597
0
    return !strcmp(config_info->filter, value);
598
0
  else if (config_info->token && !strcasecmp(field, promisor_field_token))
599
0
    return !strcmp(config_info->token, value);
600
601
0
  return 0;
602
0
}
603
604
/*
605
 * Check that the advertised fields match the local configuration.
606
 *
607
 * When 'config_entry' is NULL (ACCEPT_ALL mode), every checked field
608
 * must match at least one remote in 'config_info'.
609
 *
610
 * When 'config_entry' points to a specific remote's config, the
611
 * checked fields are compared against that single remote only.
612
 */
613
static int all_fields_match(struct promisor_info *advertised,
614
          struct string_list *config_info,
615
          struct promisor_info *config_entry)
616
0
{
617
0
  struct string_list *fields = fields_checked();
618
0
  struct string_list_item *item_checked;
619
620
0
  for_each_string_list_item(item_checked, fields) {
621
0
    int match = 0;
622
0
    const char *field = item_checked->string;
623
0
    const char *value = NULL;
624
625
0
    if (!strcasecmp(field, promisor_field_filter))
626
0
      value = advertised->filter;
627
0
    else if (!strcasecmp(field, promisor_field_token))
628
0
      value = advertised->token;
629
630
0
    if (!value)
631
0
      return 0;
632
633
0
    if (config_entry) {
634
0
      match = match_field_against_config(field, value,
635
0
                 config_entry);
636
0
    } else {
637
0
      struct string_list_item *item;
638
0
      for_each_string_list_item(item, config_info) {
639
0
        struct promisor_info *p = item->util;
640
0
        if (match_field_against_config(field, value, p)) {
641
0
          match = 1;
642
0
          break;
643
0
        }
644
0
      }
645
0
    }
646
647
0
    if (!match)
648
0
      return 0;
649
0
  }
650
651
0
  return 1;
652
0
}
653
654
static bool has_control_char(const char *s)
655
0
{
656
0
  for (const char *c = s; *c; c++)
657
0
    if (iscntrl(*c))
658
0
      return true;
659
0
  return false;
660
0
}
661
662
struct allowed_url {
663
  char *remote_name;
664
  char *url_pattern;
665
  struct url_info pattern_info;
666
};
667
668
static void allowed_url_free(void *util, const char *str UNUSED)
669
0
{
670
0
  struct allowed_url *allowed = util;
671
672
0
  if (!allowed)
673
0
    return;
674
675
  /* Depending on prefix, free either remote_name or url_pattern */
676
0
  free(allowed->remote_name ? allowed->remote_name : allowed->url_pattern);
677
0
  free(allowed->pattern_info.url);
678
0
  free(allowed);
679
0
}
680
681
static struct allowed_url *valid_accept_url(const char *url)
682
0
{
683
0
  char *dup, *p;
684
0
  struct allowed_url *allowed;
685
686
0
  if (!url)
687
0
    return NULL;
688
689
0
  dup = xstrdup(url);
690
0
  p = strchr(dup, '=');
691
0
  if (p) {
692
0
    *p = '\0';
693
0
    if (!valid_remote_name(dup)) {
694
0
      warning(_("invalid remote name '%s' before '=' sign "
695
0
          "in '%s' from promisor.acceptFromServerUrl config"),
696
0
        dup, url);
697
0
      free(dup);
698
0
      return NULL;
699
0
    }
700
0
    p++;
701
0
  } else {
702
0
    p = dup;
703
0
  }
704
705
0
  if (has_control_char(p)) {
706
0
    warning(_("invalid url pattern '%s' "
707
0
        "in '%s' from promisor.acceptFromServerUrl config"), p, url);
708
0
    free(dup);
709
0
    return NULL;
710
0
  }
711
712
0
  allowed = xmalloc(sizeof(*allowed));
713
0
  allowed->remote_name = (p == dup) ? NULL : dup;
714
0
  allowed->url_pattern = p;
715
0
  allowed->pattern_info.url = url_normalize_pattern(p, &allowed->pattern_info);
716
0
  if (!allowed->pattern_info.url) {
717
0
    warning(_("invalid url pattern '%s' "
718
0
        "in '%s' from promisor.acceptFromServerUrl config"), p, url);
719
0
    free(dup);
720
0
    free(allowed);
721
0
    return NULL;
722
0
  }
723
724
0
  return allowed;
725
0
}
726
727
static void load_accept_from_server_url(struct repository *repo,
728
          struct string_list *accept_urls)
729
0
{
730
0
  const struct string_list *config_urls;
731
732
0
  if (!repo_config_get_string_multi(repo, "promisor.acceptfromserverurl", &config_urls)) {
733
0
    struct string_list_item *item;
734
735
0
    for_each_string_list_item(item, config_urls) {
736
0
      struct allowed_url *allowed = valid_accept_url(item->string);
737
0
      if (allowed) {
738
0
        struct string_list_item *new;
739
0
        new = string_list_append(accept_urls, item->string);
740
0
        new->util = allowed;
741
0
      }
742
0
    }
743
0
  }
744
0
}
745
746
static bool match_pattern_url(const char *pat, size_t pat_len,
747
            const char *url, size_t url_len)
748
0
{
749
0
  char *p_str = xstrndup(pat, pat_len);
750
0
  char *u_str = xstrndup(url, url_len);
751
0
  bool res = !wildmatch(p_str, u_str, 0);
752
753
0
  free(p_str);
754
0
  free(u_str);
755
756
0
  return res;
757
0
}
758
759
static bool match_one_url(const struct url_info *pi, const struct url_info *ui)
760
0
{
761
0
  const char *pat = pi->url;
762
0
  const char *url = ui->url;
763
764
  /*
765
   * Schemes must match exactly. They are case-folded by
766
   * url_normalize(), so strncmp() suffices.
767
   */
768
0
  if (pi->scheme_len != ui->scheme_len || strncmp(pat, url, pi->scheme_len))
769
0
    return false;
770
771
  /*
772
   * Ports must match exactly. url_normalize() strips default
773
   * ports (like 443 for https), so length and content
774
   * comparisons are sufficient.
775
   */
776
0
  if (pi->port_len != ui->port_len ||
777
0
      strncmp(pat + pi->port_off, url + ui->port_off, pi->port_len))
778
0
    return false;
779
780
  /*
781
   * Match host and path separately to prevent a '*' in the host
782
   * portion of the pattern from matching across the '/'
783
   * boundary into the path.
784
   */
785
786
0
  return match_pattern_url(pat + pi->host_off, pi->host_len,
787
0
         url + ui->host_off, ui->host_len) &&
788
0
    match_pattern_url(pat + pi->path_off, pi->path_len,
789
0
          url + ui->path_off, ui->path_len);
790
0
}
791
792
static struct allowed_url *url_matches_accept_list(
793
    struct string_list *accept_urls, const char *url)
794
0
{
795
0
  struct string_list_item *item;
796
0
  struct url_info url_info;
797
798
0
  url_info.url = url_normalize(url, &url_info);
799
800
0
  if (!url_info.url)
801
0
    return NULL;
802
803
0
  for_each_string_list_item(item, accept_urls) {
804
0
    struct allowed_url *allowed = item->util;
805
806
0
    if (match_one_url(&allowed->pattern_info, &url_info)) {
807
0
      free(url_info.url);
808
0
      return allowed;
809
0
    }
810
0
  }
811
812
0
  free(url_info.url);
813
0
  return NULL;
814
0
}
815
816
/*
817
 * Sanitize the buffer to make it a valid remote name coming from the
818
 * server by:
819
 *
820
 * - replacing any non alphanumeric character with a '-'
821
 * - stripping any leading '-',
822
 * - condensing multiple '-' into one,
823
 * - prepending "promisor-auto-",
824
 * - validating the result.
825
 */
826
static int sanitize_remote_name(struct strbuf *buf, const char *url)
827
0
{
828
0
  char prev = '-';
829
0
  for (size_t i = 0; i < buf->len; ) {
830
0
    if (!isalnum(buf->buf[i]))
831
0
      buf->buf[i] = '-';
832
0
    if (prev == '-' && buf->buf[i] == '-') {
833
0
      strbuf_remove(buf, i, 1);
834
0
    } else {
835
0
      prev = buf->buf[i];
836
0
      i++;
837
0
    }
838
0
  }
839
840
0
  strbuf_strip_suffix(buf, "-");
841
842
0
  if (!buf->len) {
843
0
    warning(_("couldn't generate a valid remote name from "
844
0
        "advertised url '%s', ignoring this remote"), url);
845
0
    return -1;
846
0
  }
847
848
0
  strbuf_insertstr(buf, 0, "promisor-auto-");
849
850
0
  if (!valid_remote_name(buf->buf)) {
851
0
    warning(_("generated remote name '%s' from advertised url '%s' "
852
0
        "is invalid, ignoring this remote"), buf->buf, url);
853
0
    return -1;
854
0
  }
855
856
0
  return 0;
857
0
}
858
859
static char *promisor_remote_name_from_url(const char *url)
860
0
{
861
0
  struct url_info url_info = { 0 };
862
0
  char *normalized = url_normalize(url, &url_info);
863
0
  struct strbuf buf = STRBUF_INIT;
864
865
0
  if (!normalized) {
866
0
    warning(_("couldn't normalize advertised url '%s', "
867
0
        "ignoring this remote"), url);
868
0
    return NULL;
869
0
  }
870
871
0
  if (url_info.host_len) {
872
0
    strbuf_add(&buf, normalized + url_info.host_off, url_info.host_len);
873
0
    strbuf_addch(&buf, '-');
874
0
  }
875
876
0
  if (url_info.port_len) {
877
0
    strbuf_add(&buf, normalized + url_info.port_off, url_info.port_len);
878
0
    strbuf_addch(&buf, '-');
879
0
  }
880
881
0
  if (url_info.path_len) {
882
0
    strbuf_add(&buf, normalized + url_info.path_off, url_info.path_len);
883
0
    strbuf_trim_trailing_dir_sep(&buf);
884
0
    strbuf_strip_suffix(&buf, ".git");
885
0
  }
886
887
0
  free(normalized);
888
889
0
  if (sanitize_remote_name(&buf, url)) {
890
0
    strbuf_release(&buf);
891
0
    return NULL;
892
0
  }
893
894
0
  return strbuf_detach(&buf, NULL);
895
0
}
896
897
static void configure_auto_promisor_remote(struct repository *repo,
898
             const char *name,
899
             const char *url,
900
             const char *advertised_as,
901
             bool reuse)
902
0
{
903
0
  char *key;
904
905
0
  if (!reuse) {
906
0
    fprintf(stderr, _("Auto-creating promisor remote '%s' for URL '%s'\n"),
907
0
      name, url);
908
909
0
    key = xstrfmt("remote.%s.url", name);
910
0
    repo_config_set_gently(repo, key, url);
911
0
    free(key);
912
0
  }
913
914
  /* NB: when reusing, this promotes an existing non-promisor remote */
915
0
  key = xstrfmt("remote.%s.promisor", name);
916
0
  repo_config_set_gently(repo, key, "true");
917
0
  free(key);
918
919
0
  if (advertised_as) {
920
0
    key = xstrfmt("remote.%s.advertisedAs", name);
921
0
    repo_config_set_gently(repo, key, advertised_as);
922
0
    free(key);
923
0
  }
924
0
}
925
926
0
#define MAX_REMOTES_WITH_SIMILAR_NAMES 20
927
928
/* Return the allocated local name, or NULL on failure */
929
static char *handle_matching_allowed_url(struct repository *repo,
930
           char *allowed_name,
931
           const char *remote_url,
932
           const char *remote_name)
933
0
{
934
0
  char *name;
935
0
  char *basename = allowed_name ?
936
0
    xstrdup(allowed_name) :
937
0
    promisor_remote_name_from_url(remote_url);
938
0
  int i = 0;
939
0
  bool reuse = false;
940
941
0
  if (!basename)
942
0
    return NULL;
943
944
0
  name = xstrdup(basename);
945
946
0
  while (i < MAX_REMOTES_WITH_SIMILAR_NAMES) {
947
0
    char *url_key = xstrfmt("remote.%s.url", name);
948
0
    const char *existing_url;
949
0
    int exists = !repo_config_get_string_tmp(repo, url_key, &existing_url);
950
951
0
    free(url_key);
952
953
0
    if (!exists)
954
0
      break; /* Free to use */
955
956
0
    if (!strcmp(existing_url, remote_url)) {
957
0
      reuse = true;
958
0
      break; /* Same URL, so safe to reuse */
959
0
    }
960
961
0
    i++;
962
0
    free(name);
963
0
    name = xstrfmt("%s-%d", basename, i);
964
0
  }
965
966
0
  if (i < MAX_REMOTES_WITH_SIMILAR_NAMES) {
967
0
    configure_auto_promisor_remote(repo, name,
968
0
                 remote_url, remote_name,
969
0
                 reuse);
970
0
  } else {
971
0
    warning(_("too many remotes accepted with name like '%s-X', "
972
0
        "ignoring this remote"), basename);
973
0
    FREE_AND_NULL(name);
974
0
  }
975
976
0
  free(basename);
977
0
  return name;
978
0
}
979
980
static int should_accept_new_remote_url(struct repository *repo,
981
          struct string_list *accept_urls,
982
          struct promisor_info *advertised)
983
0
{
984
0
  struct allowed_url *allowed = url_matches_accept_list(accept_urls,
985
0
                   advertised->url);
986
0
  if (allowed) {
987
0
    char *name = handle_matching_allowed_url(repo,
988
0
               allowed->remote_name,
989
0
               advertised->url,
990
0
               advertised->name);
991
0
    if (name) {
992
0
      free((char *)advertised->local_name);
993
0
      advertised->local_name = name;
994
0
      return 1;
995
0
    }
996
0
  }
997
998
0
  return 0;
999
0
}
1000
1001
static int should_accept_remote(struct repository *repo,
1002
        enum accept_promisor accept,
1003
        struct promisor_info *advertised,
1004
        struct string_list *accept_urls,
1005
        struct string_list *config_info,
1006
        bool *reload_config)
1007
0
{
1008
0
  struct promisor_info *p;
1009
0
  struct string_list_item *item;
1010
0
  const char *remote_name = advertised->name;
1011
0
  const char *remote_url = advertised->url;
1012
1013
0
  if (!remote_url || !*remote_url)
1014
0
    BUG("no or empty URL advertised for remote '%s'; "
1015
0
        "this remote should have been rejected earlier",
1016
0
        remote_name);
1017
1018
  /* Get config info for that promisor remote */
1019
0
  item = string_list_lookup(config_info, remote_name);
1020
1021
0
  if (!item) {
1022
    /* We don't know about that remote */
1023
1024
0
    int res = should_accept_new_remote_url(repo, accept_urls, advertised);
1025
0
    if (res) {
1026
0
      *reload_config = true;
1027
0
      return res;
1028
0
    }
1029
1030
0
    if (accept == ACCEPT_ALL)
1031
0
      return all_fields_match(advertised, config_info, NULL);
1032
0
    return 0;
1033
0
  }
1034
1035
0
  p = item->util;
1036
1037
  /* Known remote in the allowlist? */
1038
0
  if (!strcmp(p->url, remote_url) && url_matches_accept_list(accept_urls, remote_url))
1039
0
    return all_fields_match(advertised, config_info, p);
1040
1041
0
  if (accept == ACCEPT_ALL)
1042
0
    return all_fields_match(advertised, config_info, NULL);
1043
1044
0
  if (accept == ACCEPT_KNOWN_NAME)
1045
0
    return all_fields_match(advertised, config_info, p);
1046
1047
0
  if (strcmp(p->url, remote_url)) {
1048
0
    warning(_("known remote named '%s' but with URL '%s' instead of '%s', "
1049
0
        "ignoring this remote"),
1050
0
      remote_name, p->url, remote_url);
1051
0
    return 0;
1052
0
  }
1053
1054
0
  if (accept == ACCEPT_KNOWN_URL)
1055
0
    return all_fields_match(advertised, config_info, p);
1056
1057
0
  if (accept != ACCEPT_NONE)
1058
0
    BUG("Unhandled 'enum accept_promisor' value '%d'", accept);
1059
1060
0
  return 0;
1061
0
}
1062
1063
static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value)
1064
0
{
1065
0
  const char *p;
1066
0
  if (!skip_prefix(elem, field_name, &p) || *p != '=')
1067
0
    return 0;
1068
0
  *value = p + 1;
1069
0
  return 1;
1070
0
}
1071
1072
static struct promisor_info *parse_one_advertised_remote(const char *remote_info)
1073
0
{
1074
0
  struct promisor_info *info = xcalloc(1, sizeof(*info));
1075
0
  struct string_list elem_list = STRING_LIST_INIT_DUP;
1076
0
  struct string_list_item *item;
1077
1078
0
  string_list_split(&elem_list, remote_info, ",", -1);
1079
1080
0
  for_each_string_list_item(item, &elem_list) {
1081
0
    const char *elem = item->string;
1082
0
    const char *p = strchr(elem, '=');
1083
1084
0
    if (!p) {
1085
0
      warning(_("invalid element '%s' from remote info"), elem);
1086
0
      continue;
1087
0
    }
1088
1089
0
    if (skip_field_name_prefix(elem, promisor_field_name, &p))
1090
0
      info->name = url_percent_decode(p);
1091
0
    else if (skip_field_name_prefix(elem, promisor_field_url, &p))
1092
0
      info->url = url_percent_decode(p);
1093
0
    else if (skip_field_name_prefix(elem, promisor_field_filter, &p))
1094
0
      info->filter = url_percent_decode(p);
1095
0
    else if (skip_field_name_prefix(elem, promisor_field_token, &p))
1096
0
      info->token = url_percent_decode(p);
1097
0
  }
1098
1099
0
  string_list_clear(&elem_list, 0);
1100
1101
0
  if (!info->name || !*info->name || !info->url || !*info->url) {
1102
0
    warning(_("server advertised a promisor remote without a name or URL: '%s', "
1103
0
        "ignoring this remote"), remote_info);
1104
0
    promisor_info_free(info);
1105
0
    return NULL;
1106
0
  }
1107
1108
0
  return info;
1109
0
}
1110
1111
static bool store_one_field(struct repository *repo, const char *remote_name,
1112
          const char *field_name, const char *field_key,
1113
          const char *advertised, const char *current)
1114
0
{
1115
0
  if (advertised && (!current || strcmp(current, advertised))) {
1116
0
    char *key = xstrfmt("remote.%s.%s", remote_name, field_key);
1117
1118
0
    fprintf(stderr, _("Storing new %s from server for remote '%s'.\n"
1119
0
          "    '%s' -> '%s'\n"),
1120
0
      field_name, remote_name,
1121
0
      current ? current : "",
1122
0
      advertised);
1123
1124
0
    repo_config_set_gently(repo, key, advertised);
1125
0
    free(key);
1126
1127
0
    return true;
1128
0
  }
1129
1130
0
  return false;
1131
0
}
1132
1133
/* Check that a filter is valid by parsing it */
1134
static bool valid_filter(const char *filter, const char *remote_name)
1135
0
{
1136
0
  struct list_objects_filter_options filter_opts = LIST_OBJECTS_FILTER_INIT;
1137
0
  struct strbuf err = STRBUF_INIT;
1138
0
  int res = gently_parse_list_objects_filter(&filter_opts, filter, &err);
1139
1140
0
  if (res)
1141
0
    warning(_("invalid filter '%s' for remote '%s' "
1142
0
        "will not be stored: %s"),
1143
0
      filter, remote_name, err.buf);
1144
1145
0
  list_objects_filter_release(&filter_opts);
1146
0
  strbuf_release(&err);
1147
1148
0
  return !res;
1149
0
}
1150
1151
static bool valid_token(const char *token, const char *remote_name)
1152
0
{
1153
0
  if (has_control_char(token)) {
1154
0
    warning(_("invalid token '%s' for remote '%s' "
1155
0
        "will not be stored"),
1156
0
      token, remote_name);
1157
0
    return false;
1158
0
  }
1159
1160
0
  return true;
1161
0
}
1162
1163
struct store_info {
1164
  struct repository *repo;
1165
  struct string_list config_info;
1166
  bool store_filter;
1167
  bool store_token;
1168
};
1169
1170
static struct store_info *store_info_new(struct repository *repo)
1171
0
{
1172
0
  struct string_list *fields_to_store = fields_stored();
1173
0
  struct store_info *s = xmalloc(sizeof(*s));
1174
1175
0
  s->repo = repo;
1176
1177
0
  string_list_init_nodup(&s->config_info);
1178
0
  promisor_config_info_list(repo, &s->config_info, fields_to_store);
1179
0
  string_list_sort(&s->config_info);
1180
1181
0
  s->store_filter = !!string_list_lookup(fields_to_store, promisor_field_filter);
1182
0
  s->store_token = !!string_list_lookup(fields_to_store, promisor_field_token);
1183
1184
0
  return s;
1185
0
}
1186
1187
static void store_info_free(struct store_info *s)
1188
0
{
1189
0
  if (s) {
1190
0
    promisor_info_list_clear(&s->config_info);
1191
0
    free(s);
1192
0
  }
1193
0
}
1194
1195
static bool promisor_store_advertised_fields(struct promisor_info *advertised,
1196
               struct store_info *store_info)
1197
0
{
1198
0
  struct promisor_info *p;
1199
0
  struct string_list_item *item;
1200
0
  const char *remote_name = promisor_info_local_name(advertised);
1201
0
  bool reload_config = false;
1202
1203
0
  if (!(store_info->store_filter || store_info->store_token))
1204
0
    return false;
1205
1206
  /*
1207
   * Get existing config info for the advertised promisor
1208
   * remote. This ensures the remote is already configured on
1209
   * the client side.
1210
   */
1211
0
  item = string_list_lookup(&store_info->config_info, remote_name);
1212
1213
0
  if (!item)
1214
0
    return false;
1215
1216
0
  p = item->util;
1217
1218
0
  if (store_info->store_filter && advertised->filter &&
1219
0
      valid_filter(advertised->filter, remote_name))
1220
0
    reload_config |= store_one_field(store_info->repo, remote_name,
1221
0
             "filter", promisor_field_filter,
1222
0
             advertised->filter, p->filter);
1223
1224
0
  if (store_info->store_token && advertised->token &&
1225
0
      valid_token(advertised->token, remote_name))
1226
0
    reload_config |= store_one_field(store_info->repo, remote_name,
1227
0
             "token", promisor_field_token,
1228
0
             advertised->token, p->token);
1229
1230
0
  return reload_config;
1231
0
}
1232
1233
static enum accept_promisor accept_from_server(struct repository *repo)
1234
0
{
1235
0
  const char *accept_str;
1236
0
  enum accept_promisor accept = ACCEPT_NONE;
1237
1238
0
  if (!repo_config_get_string_tmp(repo, "promisor.acceptfromserver", &accept_str)) {
1239
0
    if (!*accept_str || !strcasecmp("None", accept_str))
1240
0
      accept = ACCEPT_NONE;
1241
0
    else if (!strcasecmp("KnownUrl", accept_str))
1242
0
      accept = ACCEPT_KNOWN_URL;
1243
0
    else if (!strcasecmp("KnownName", accept_str))
1244
0
      accept = ACCEPT_KNOWN_NAME;
1245
0
    else if (!strcasecmp("All", accept_str))
1246
0
      accept = ACCEPT_ALL;
1247
0
    else
1248
0
      warning(_("unknown '%s' value for '%s' config option"),
1249
0
        accept_str, "promisor.acceptfromserver");
1250
0
  }
1251
1252
0
  return accept;
1253
0
}
1254
1255
static void filter_promisor_remote(struct repository *repo,
1256
           struct string_list *accepted_remotes,
1257
           const char *info)
1258
0
{
1259
0
  struct string_list config_info = STRING_LIST_INIT_NODUP;
1260
0
  struct string_list remote_info = STRING_LIST_INIT_DUP;
1261
0
  struct store_info *store_info = NULL;
1262
0
  struct string_list_item *item;
1263
0
  bool reload_config = false;
1264
0
  enum accept_promisor accept = accept_from_server(repo);
1265
0
  struct string_list accept_urls = STRING_LIST_INIT_DUP;
1266
1267
  /* Load and validate the acceptFromServerUrl config */
1268
0
  load_accept_from_server_url(repo, &accept_urls);
1269
1270
0
  if (accept == ACCEPT_NONE && !accept_urls.nr)
1271
0
    return;
1272
1273
  /* Parse remote info received */
1274
1275
0
  string_list_split(&remote_info, info, ";", -1);
1276
1277
0
  for_each_string_list_item(item, &remote_info) {
1278
0
    struct promisor_info *advertised;
1279
1280
0
    advertised = parse_one_advertised_remote(item->string);
1281
1282
0
    if (!advertised)
1283
0
      continue;
1284
1285
0
    if (!config_info.nr) {
1286
0
      promisor_config_info_list(repo, &config_info, fields_checked());
1287
0
      string_list_sort(&config_info);
1288
0
    }
1289
1290
0
    if (should_accept_remote(repo, accept, advertised, &accept_urls,
1291
0
           &config_info, &reload_config)) {
1292
0
      if (!store_info)
1293
0
        store_info = store_info_new(repo);
1294
0
      if (promisor_store_advertised_fields(advertised, store_info))
1295
0
        reload_config = true;
1296
1297
0
      string_list_append(accepted_remotes, advertised->name)->util = advertised;
1298
0
    } else {
1299
0
      promisor_info_free(advertised);
1300
0
    }
1301
0
  }
1302
1303
0
  string_list_clear_func(&accept_urls, allowed_url_free);
1304
0
  promisor_info_list_clear(&config_info);
1305
0
  string_list_clear(&remote_info, 0);
1306
0
  store_info_free(store_info);
1307
1308
0
  if (reload_config)
1309
0
    repo_promisor_remote_reinit(repo);
1310
1311
  /* Apply accepted remotes to the stable repo state */
1312
0
  for_each_string_list_item(item, accepted_remotes) {
1313
0
    struct promisor_info *info = item->util;
1314
0
    const char *remote_name = promisor_info_local_name(info);
1315
0
    struct promisor_remote *r = repo_promisor_remote_find(repo, remote_name);
1316
1317
0
    if (r) {
1318
0
      r->accepted = 1;
1319
0
      if (info->filter) {
1320
0
        free(r->advertised_filter);
1321
0
        r->advertised_filter = xstrdup(info->filter);
1322
0
      }
1323
0
    }
1324
0
  }
1325
0
}
1326
1327
void promisor_remote_reply(const char *info, char **accepted_out)
1328
0
{
1329
0
  struct string_list accepted_remotes = STRING_LIST_INIT_NODUP;
1330
1331
0
  filter_promisor_remote(the_repository, &accepted_remotes, info);
1332
1333
0
  if (accepted_out) {
1334
0
    if (accepted_remotes.nr) {
1335
0
      struct strbuf reply = STRBUF_INIT;
1336
0
      struct string_list_item *item;
1337
1338
0
      for_each_string_list_item(item, &accepted_remotes) {
1339
0
        if (reply.len)
1340
0
          strbuf_addch(&reply, ';');
1341
0
        strbuf_addstr_urlencode(&reply, item->string, allow_unsanitized);
1342
0
      }
1343
0
      *accepted_out = strbuf_detach(&reply, NULL);
1344
0
    } else {
1345
0
      *accepted_out = NULL;
1346
0
    }
1347
0
  }
1348
1349
0
  promisor_info_list_clear(&accepted_remotes);
1350
0
}
1351
1352
void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes)
1353
0
{
1354
0
  struct string_list accepted_remotes = STRING_LIST_INIT_DUP;
1355
0
  struct string_list_item *item;
1356
1357
0
  string_list_split(&accepted_remotes, remotes, ";", -1);
1358
1359
0
  for_each_string_list_item(item, &accepted_remotes) {
1360
0
    char *decoded_remote = url_percent_decode(item->string);
1361
0
    struct promisor_remote *p = repo_promisor_remote_find(r, decoded_remote);
1362
1363
0
    if (p)
1364
0
      p->accepted = 1;
1365
0
    else
1366
0
      warning(_("accepted promisor remote '%s' not found"),
1367
0
        decoded_remote);
1368
1369
0
    free(decoded_remote);
1370
0
  }
1371
1372
0
  string_list_clear(&accepted_remotes, 0);
1373
0
}
1374
1375
char *promisor_remote_construct_filter(struct repository *repo)
1376
0
{
1377
0
  struct promisor_remote *r;
1378
0
  struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
1379
0
  struct strbuf err = STRBUF_INIT;
1380
0
  char *result = NULL;
1381
1382
0
  promisor_remote_init(repo);
1383
1384
0
  for (r = repo->promisor_remote_config->promisors; r; r = r->next) {
1385
0
    if (r->accepted && r->advertised_filter)
1386
0
      if (gently_parse_list_objects_filter(&filter_options,
1387
0
                   r->advertised_filter,
1388
0
                   &err)) {
1389
0
        warning(_("promisor remote '%s' advertised invalid filter '%s': %s"),
1390
0
          r->name, r->advertised_filter, err.buf);
1391
0
        strbuf_reset(&err);
1392
0
        continue;
1393
0
      }
1394
0
  }
1395
1396
0
  if (filter_options.choice)
1397
0
    result = xstrdup(expand_list_objects_filter_spec(&filter_options));
1398
1399
0
  list_objects_filter_release(&filter_options);
1400
0
  strbuf_release(&err);
1401
1402
0
  return result;
1403
0
}