Coverage Report

Created: 2026-11-12 06:03

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