/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 "version.h" |
16 | | |
17 | | struct promisor_remote_config { |
18 | | struct promisor_remote *promisors; |
19 | | struct promisor_remote **promisors_tail; |
20 | | }; |
21 | | |
22 | | static int fetch_objects(struct repository *repo, |
23 | | const char *remote_name, |
24 | | const struct object_id *oids, |
25 | | int oid_nr) |
26 | 0 | { |
27 | 0 | struct child_process child = CHILD_PROCESS_INIT; |
28 | 0 | int i; |
29 | 0 | FILE *child_in; |
30 | 0 | int quiet; |
31 | |
|
32 | 0 | if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) { |
33 | 0 | static int warning_shown; |
34 | 0 | if (!warning_shown) { |
35 | 0 | warning_shown = 1; |
36 | 0 | warning(_("lazy fetching disabled; some objects may not be available")); |
37 | 0 | } |
38 | 0 | return -1; |
39 | 0 | } |
40 | | |
41 | 0 | child.git_cmd = 1; |
42 | 0 | child.in = -1; |
43 | 0 | if (repo != the_repository) |
44 | 0 | prepare_other_repo_env(&child.env, repo->gitdir); |
45 | 0 | strvec_pushl(&child.args, "-c", "fetch.negotiationAlgorithm=noop", |
46 | 0 | "fetch", remote_name, "--no-tags", |
47 | 0 | "--no-write-fetch-head", "--recurse-submodules=no", |
48 | 0 | "--filter=blob:none", "--stdin", NULL); |
49 | 0 | if (!repo_config_get_bool(the_repository, "promisor.quiet", &quiet) && quiet) |
50 | 0 | strvec_push(&child.args, "--quiet"); |
51 | 0 | if (start_command(&child)) |
52 | 0 | die(_("promisor-remote: unable to fork off fetch subprocess")); |
53 | 0 | child_in = xfdopen(child.in, "w"); |
54 | |
|
55 | 0 | trace2_data_intmax("promisor", repo, "fetch_count", oid_nr); |
56 | |
|
57 | 0 | for (i = 0; i < oid_nr; i++) { |
58 | 0 | if (fputs(oid_to_hex(&oids[i]), child_in) < 0) |
59 | 0 | die_errno(_("promisor-remote: could not write to fetch subprocess")); |
60 | 0 | if (fputc('\n', child_in) < 0) |
61 | 0 | die_errno(_("promisor-remote: could not write to fetch subprocess")); |
62 | 0 | } |
63 | | |
64 | 0 | if (fclose(child_in) < 0) |
65 | 0 | die_errno(_("promisor-remote: could not close stdin to fetch subprocess")); |
66 | 0 | return finish_command(&child) ? -1 : 0; |
67 | 0 | } |
68 | | |
69 | | static struct promisor_remote *promisor_remote_new(struct promisor_remote_config *config, |
70 | | const char *remote_name) |
71 | 0 | { |
72 | 0 | struct promisor_remote *r; |
73 | |
|
74 | 0 | if (*remote_name == '/') { |
75 | 0 | warning(_("promisor remote name cannot begin with '/': %s"), |
76 | 0 | remote_name); |
77 | 0 | return NULL; |
78 | 0 | } |
79 | | |
80 | 0 | FLEX_ALLOC_STR(r, name, remote_name); |
81 | |
|
82 | 0 | *config->promisors_tail = r; |
83 | 0 | config->promisors_tail = &r->next; |
84 | |
|
85 | 0 | return r; |
86 | 0 | } |
87 | | |
88 | | static struct promisor_remote *promisor_remote_lookup(struct promisor_remote_config *config, |
89 | | const char *remote_name, |
90 | | struct promisor_remote **previous) |
91 | 0 | { |
92 | 0 | struct promisor_remote *r, *p; |
93 | |
|
94 | 0 | for (p = NULL, r = config->promisors; r; p = r, r = r->next) |
95 | 0 | if (!strcmp(r->name, remote_name)) { |
96 | 0 | if (previous) |
97 | 0 | *previous = p; |
98 | 0 | return r; |
99 | 0 | } |
100 | | |
101 | 0 | return NULL; |
102 | 0 | } |
103 | | |
104 | | static void promisor_remote_move_to_tail(struct promisor_remote_config *config, |
105 | | struct promisor_remote *r, |
106 | | struct promisor_remote *previous) |
107 | 0 | { |
108 | 0 | if (!r->next) |
109 | 0 | return; |
110 | | |
111 | 0 | if (previous) |
112 | 0 | previous->next = r->next; |
113 | 0 | else |
114 | 0 | config->promisors = r->next ? r->next : r; |
115 | 0 | r->next = NULL; |
116 | 0 | *config->promisors_tail = r; |
117 | 0 | config->promisors_tail = &r->next; |
118 | 0 | } |
119 | | |
120 | | static int promisor_remote_config(const char *var, const char *value, |
121 | | const struct config_context *ctx UNUSED, |
122 | | void *data) |
123 | 0 | { |
124 | 0 | struct promisor_remote_config *config = data; |
125 | 0 | const char *name; |
126 | 0 | size_t namelen; |
127 | 0 | const char *subkey; |
128 | |
|
129 | 0 | if (parse_config_key(var, "remote", &name, &namelen, &subkey) < 0) |
130 | 0 | return 0; |
131 | | |
132 | 0 | if (!strcmp(subkey, "promisor")) { |
133 | 0 | char *remote_name; |
134 | |
|
135 | 0 | if (!git_config_bool(var, value)) |
136 | 0 | return 0; |
137 | | |
138 | 0 | remote_name = xmemdupz(name, namelen); |
139 | |
|
140 | 0 | if (!promisor_remote_lookup(config, remote_name, NULL)) |
141 | 0 | promisor_remote_new(config, remote_name); |
142 | |
|
143 | 0 | free(remote_name); |
144 | 0 | return 0; |
145 | 0 | } |
146 | 0 | if (!strcmp(subkey, "partialclonefilter")) { |
147 | 0 | struct promisor_remote *r; |
148 | 0 | char *remote_name = xmemdupz(name, namelen); |
149 | |
|
150 | 0 | r = promisor_remote_lookup(config, remote_name, NULL); |
151 | 0 | if (!r) |
152 | 0 | r = promisor_remote_new(config, remote_name); |
153 | |
|
154 | 0 | free(remote_name); |
155 | |
|
156 | 0 | if (!r) |
157 | 0 | return 0; |
158 | | |
159 | 0 | FREE_AND_NULL(r->partial_clone_filter); |
160 | 0 | return git_config_string(&r->partial_clone_filter, var, value); |
161 | 0 | } |
162 | | |
163 | 0 | return 0; |
164 | 0 | } |
165 | | |
166 | | static void promisor_remote_init(struct repository *r) |
167 | 0 | { |
168 | 0 | struct promisor_remote_config *config; |
169 | |
|
170 | 0 | if (r->promisor_remote_config) |
171 | 0 | return; |
172 | 0 | config = r->promisor_remote_config = |
173 | 0 | xcalloc(1, sizeof(*r->promisor_remote_config)); |
174 | 0 | config->promisors_tail = &config->promisors; |
175 | |
|
176 | 0 | repo_config(r, promisor_remote_config, config); |
177 | |
|
178 | 0 | if (r->repository_format_partial_clone) { |
179 | 0 | struct promisor_remote *o, *previous; |
180 | |
|
181 | 0 | o = promisor_remote_lookup(config, |
182 | 0 | r->repository_format_partial_clone, |
183 | 0 | &previous); |
184 | 0 | if (o) |
185 | 0 | promisor_remote_move_to_tail(config, o, previous); |
186 | 0 | else |
187 | 0 | promisor_remote_new(config, r->repository_format_partial_clone); |
188 | 0 | } |
189 | 0 | } |
190 | | |
191 | | void promisor_remote_clear(struct promisor_remote_config *config) |
192 | 0 | { |
193 | 0 | while (config->promisors) { |
194 | 0 | struct promisor_remote *r = config->promisors; |
195 | 0 | free(r->partial_clone_filter); |
196 | 0 | config->promisors = config->promisors->next; |
197 | 0 | free(r); |
198 | 0 | } |
199 | |
|
200 | 0 | config->promisors_tail = &config->promisors; |
201 | 0 | } |
202 | | |
203 | | void repo_promisor_remote_reinit(struct repository *r) |
204 | 0 | { |
205 | 0 | promisor_remote_clear(r->promisor_remote_config); |
206 | 0 | FREE_AND_NULL(r->promisor_remote_config); |
207 | 0 | promisor_remote_init(r); |
208 | 0 | } |
209 | | |
210 | | struct promisor_remote *repo_promisor_remote_find(struct repository *r, |
211 | | const char *remote_name) |
212 | 0 | { |
213 | 0 | promisor_remote_init(r); |
214 | |
|
215 | 0 | if (!remote_name) |
216 | 0 | return r->promisor_remote_config->promisors; |
217 | | |
218 | 0 | return promisor_remote_lookup(r->promisor_remote_config, remote_name, NULL); |
219 | 0 | } |
220 | | |
221 | | int repo_has_promisor_remote(struct repository *r) |
222 | 0 | { |
223 | 0 | return !!repo_promisor_remote_find(r, NULL); |
224 | 0 | } |
225 | | |
226 | | int repo_has_accepted_promisor_remote(struct repository *r) |
227 | 0 | { |
228 | 0 | struct promisor_remote *p; |
229 | |
|
230 | 0 | promisor_remote_init(r); |
231 | |
|
232 | 0 | for (p = r->promisor_remote_config->promisors; p; p = p->next) |
233 | 0 | if (p->accepted) |
234 | 0 | return 1; |
235 | 0 | return 0; |
236 | 0 | } |
237 | | |
238 | | static int remove_fetched_oids(struct repository *repo, |
239 | | struct object_id **oids, |
240 | | int oid_nr, int to_free) |
241 | 0 | { |
242 | 0 | int i, remaining_nr = 0; |
243 | 0 | int *remaining = xcalloc(oid_nr, sizeof(*remaining)); |
244 | 0 | struct object_id *old_oids = *oids; |
245 | 0 | struct object_id *new_oids; |
246 | |
|
247 | 0 | for (i = 0; i < oid_nr; i++) |
248 | 0 | if (odb_read_object_info_extended(repo->objects, &old_oids[i], NULL, |
249 | 0 | OBJECT_INFO_SKIP_FETCH_OBJECT)) { |
250 | 0 | remaining[i] = 1; |
251 | 0 | remaining_nr++; |
252 | 0 | } |
253 | |
|
254 | 0 | if (remaining_nr) { |
255 | 0 | int j = 0; |
256 | 0 | CALLOC_ARRAY(new_oids, remaining_nr); |
257 | 0 | for (i = 0; i < oid_nr; i++) |
258 | 0 | if (remaining[i]) |
259 | 0 | oidcpy(&new_oids[j++], &old_oids[i]); |
260 | 0 | *oids = new_oids; |
261 | 0 | if (to_free) |
262 | 0 | free(old_oids); |
263 | 0 | } |
264 | |
|
265 | 0 | free(remaining); |
266 | |
|
267 | 0 | return remaining_nr; |
268 | 0 | } |
269 | | |
270 | | void promisor_remote_get_direct(struct repository *repo, |
271 | | const struct object_id *oids, |
272 | | int oid_nr) |
273 | 0 | { |
274 | 0 | struct promisor_remote *r; |
275 | 0 | struct object_id *remaining_oids = (struct object_id *)oids; |
276 | 0 | int remaining_nr = oid_nr; |
277 | 0 | int to_free = 0; |
278 | 0 | int i; |
279 | |
|
280 | 0 | if (oid_nr == 0) |
281 | 0 | return; |
282 | | |
283 | 0 | promisor_remote_init(repo); |
284 | |
|
285 | 0 | for (r = repo->promisor_remote_config->promisors; r; r = r->next) { |
286 | 0 | if (fetch_objects(repo, r->name, remaining_oids, remaining_nr) < 0) { |
287 | 0 | if (remaining_nr == 1) |
288 | 0 | continue; |
289 | 0 | remaining_nr = remove_fetched_oids(repo, &remaining_oids, |
290 | 0 | remaining_nr, to_free); |
291 | 0 | if (remaining_nr) { |
292 | 0 | to_free = 1; |
293 | 0 | continue; |
294 | 0 | } |
295 | 0 | } |
296 | 0 | goto all_fetched; |
297 | 0 | } |
298 | | |
299 | 0 | for (i = 0; i < remaining_nr; i++) { |
300 | 0 | if (is_promisor_object(repo, &remaining_oids[i])) |
301 | 0 | die(_("could not fetch %s from promisor remote"), |
302 | 0 | oid_to_hex(&remaining_oids[i])); |
303 | 0 | } |
304 | | |
305 | 0 | all_fetched: |
306 | 0 | if (to_free) |
307 | 0 | free(remaining_oids); |
308 | 0 | } |
309 | | |
310 | | static int allow_unsanitized(char ch) |
311 | 0 | { |
312 | 0 | if (ch == ',' || ch == ';' || ch == '%') |
313 | 0 | return 0; |
314 | 0 | return ch > 32 && ch < 127; |
315 | 0 | } |
316 | | |
317 | | /* |
318 | | * All the fields used in "promisor-remote" protocol capability, |
319 | | * including the mandatory "name" and "url" ones. |
320 | | */ |
321 | | static const char promisor_field_name[] = "name"; |
322 | | static const char promisor_field_url[] = "url"; |
323 | | static const char promisor_field_filter[] = "partialCloneFilter"; |
324 | | static const char promisor_field_token[] = "token"; |
325 | | |
326 | | /* |
327 | | * List of optional field names that can be used in the |
328 | | * "promisor-remote" protocol capability (others must be |
329 | | * ignored). Each field should correspond to a configurable property |
330 | | * of a remote that can be relevant for the client. |
331 | | */ |
332 | | static const char *known_fields[] = { |
333 | | promisor_field_filter, /* Filter used for partial clone */ |
334 | | promisor_field_token, /* Authentication token for the remote */ |
335 | | NULL |
336 | | }; |
337 | | |
338 | | /* |
339 | | * Check if 'field' is in the list of the known field names for the |
340 | | * "promisor-remote" protocol capability. |
341 | | */ |
342 | | static int is_known_field(const char *field) |
343 | 0 | { |
344 | 0 | const char **p; |
345 | |
|
346 | 0 | for (p = known_fields; *p; p++) |
347 | 0 | if (!strcasecmp(*p, field)) |
348 | 0 | return 1; |
349 | 0 | return 0; |
350 | 0 | } |
351 | | |
352 | | static int is_valid_field(struct string_list_item *item, void *cb_data) |
353 | 0 | { |
354 | 0 | const char *field = item->string; |
355 | 0 | const char *config_key = (const char *)cb_data; |
356 | |
|
357 | 0 | if (!is_known_field(field)) { |
358 | 0 | warning(_("unsupported field '%s' in '%s' config"), field, config_key); |
359 | 0 | return 0; |
360 | 0 | } |
361 | 0 | return 1; |
362 | 0 | } |
363 | | |
364 | | static char *fields_from_config(struct string_list *fields_list, const char *config_key) |
365 | 0 | { |
366 | 0 | char *fields = NULL; |
367 | |
|
368 | 0 | if (!repo_config_get_string(the_repository, config_key, &fields) && *fields) { |
369 | 0 | string_list_split_in_place_f(fields_list, fields, ",", -1, |
370 | 0 | STRING_LIST_SPLIT_TRIM | |
371 | 0 | STRING_LIST_SPLIT_NONEMPTY); |
372 | 0 | filter_string_list(fields_list, 0, is_valid_field, (void *)config_key); |
373 | 0 | } |
374 | |
|
375 | 0 | return fields; |
376 | 0 | } |
377 | | |
378 | | static struct string_list *fields_sent(void) |
379 | 0 | { |
380 | 0 | static struct string_list fields_list = STRING_LIST_INIT_NODUP; |
381 | 0 | static int initialized; |
382 | |
|
383 | 0 | if (!initialized) { |
384 | 0 | fields_list.cmp = strcasecmp; |
385 | 0 | fields_from_config(&fields_list, "promisor.sendFields"); |
386 | 0 | initialized = 1; |
387 | 0 | } |
388 | |
|
389 | 0 | return &fields_list; |
390 | 0 | } |
391 | | |
392 | | static struct string_list *fields_checked(void) |
393 | 0 | { |
394 | 0 | static struct string_list fields_list = STRING_LIST_INIT_NODUP; |
395 | 0 | static int initialized; |
396 | |
|
397 | 0 | if (!initialized) { |
398 | 0 | fields_list.cmp = strcasecmp; |
399 | 0 | fields_from_config(&fields_list, "promisor.checkFields"); |
400 | 0 | initialized = 1; |
401 | 0 | } |
402 | |
|
403 | 0 | return &fields_list; |
404 | 0 | } |
405 | | |
406 | | /* |
407 | | * Struct for promisor remotes involved in the "promisor-remote" |
408 | | * protocol capability. |
409 | | * |
410 | | * Except for "name", each <member> in this struct and its <value> |
411 | | * should correspond (either on the client side or on the server side) |
412 | | * to a "remote.<name>.<member>" config variable set to <value> where |
413 | | * "<name>" is a promisor remote name. |
414 | | */ |
415 | | struct promisor_info { |
416 | | const char *name; |
417 | | const char *url; |
418 | | const char *filter; |
419 | | const char *token; |
420 | | }; |
421 | | |
422 | | static void promisor_info_free(struct promisor_info *p) |
423 | 0 | { |
424 | 0 | free((char *)p->name); |
425 | 0 | free((char *)p->url); |
426 | 0 | free((char *)p->filter); |
427 | 0 | free((char *)p->token); |
428 | 0 | free(p); |
429 | 0 | } |
430 | | |
431 | | static void promisor_info_list_clear(struct string_list *list) |
432 | 0 | { |
433 | 0 | for (size_t i = 0; i < list->nr; i++) |
434 | 0 | promisor_info_free(list->items[i].util); |
435 | 0 | string_list_clear(list, 0); |
436 | 0 | } |
437 | | |
438 | | static void set_one_field(struct promisor_info *p, |
439 | | const char *field, const char *value) |
440 | 0 | { |
441 | 0 | if (!strcasecmp(field, promisor_field_filter)) |
442 | 0 | p->filter = xstrdup(value); |
443 | 0 | else if (!strcasecmp(field, promisor_field_token)) |
444 | 0 | p->token = xstrdup(value); |
445 | 0 | else |
446 | 0 | BUG("invalid field '%s'", field); |
447 | 0 | } |
448 | | |
449 | | static void set_fields(struct promisor_info *p, |
450 | | struct string_list *field_names) |
451 | 0 | { |
452 | 0 | struct string_list_item *item; |
453 | |
|
454 | 0 | for_each_string_list_item(item, field_names) { |
455 | 0 | char *key = xstrfmt("remote.%s.%s", p->name, item->string); |
456 | 0 | const char *val; |
457 | 0 | if (!repo_config_get_string_tmp(the_repository, key, &val) && *val) |
458 | 0 | set_one_field(p, item->string, val); |
459 | 0 | free(key); |
460 | 0 | } |
461 | 0 | } |
462 | | |
463 | | /* |
464 | | * Populate 'list' with promisor remote information from the config. |
465 | | * The 'util' pointer of each list item will hold a 'struct |
466 | | * promisor_info'. Except "name" and "url", only members of that |
467 | | * struct specified by the 'field_names' list are set (using values |
468 | | * from the configuration). |
469 | | */ |
470 | | static void promisor_config_info_list(struct repository *repo, |
471 | | struct string_list *list, |
472 | | struct string_list *field_names) |
473 | 0 | { |
474 | 0 | struct promisor_remote *r; |
475 | |
|
476 | 0 | promisor_remote_init(repo); |
477 | |
|
478 | 0 | for (r = repo->promisor_remote_config->promisors; r; r = r->next) { |
479 | 0 | const char *url; |
480 | 0 | char *url_key = xstrfmt("remote.%s.url", r->name); |
481 | | |
482 | | /* Only add remotes with a non empty URL */ |
483 | 0 | if (!repo_config_get_string_tmp(the_repository, url_key, &url) && *url) { |
484 | 0 | struct promisor_info *new_info = xcalloc(1, sizeof(*new_info)); |
485 | 0 | struct string_list_item *item; |
486 | |
|
487 | 0 | new_info->name = xstrdup(r->name); |
488 | 0 | new_info->url = xstrdup(url); |
489 | |
|
490 | 0 | if (field_names) |
491 | 0 | set_fields(new_info, field_names); |
492 | |
|
493 | 0 | item = string_list_append(list, new_info->name); |
494 | 0 | item->util = new_info; |
495 | 0 | } |
496 | |
|
497 | 0 | free(url_key); |
498 | 0 | } |
499 | 0 | } |
500 | | |
501 | | char *promisor_remote_info(struct repository *repo) |
502 | 0 | { |
503 | 0 | struct strbuf sb = STRBUF_INIT; |
504 | 0 | int advertise_promisors = 0; |
505 | 0 | struct string_list config_info = STRING_LIST_INIT_NODUP; |
506 | 0 | struct string_list_item *item; |
507 | |
|
508 | 0 | repo_config_get_bool(the_repository, "promisor.advertise", &advertise_promisors); |
509 | |
|
510 | 0 | if (!advertise_promisors) |
511 | 0 | return NULL; |
512 | | |
513 | 0 | promisor_config_info_list(repo, &config_info, fields_sent()); |
514 | |
|
515 | 0 | if (!config_info.nr) |
516 | 0 | return NULL; |
517 | | |
518 | 0 | for_each_string_list_item(item, &config_info) { |
519 | 0 | struct promisor_info *p = item->util; |
520 | |
|
521 | 0 | if (item != config_info.items) |
522 | 0 | strbuf_addch(&sb, ';'); |
523 | |
|
524 | 0 | strbuf_addf(&sb, "%s=", promisor_field_name); |
525 | 0 | strbuf_addstr_urlencode(&sb, p->name, allow_unsanitized); |
526 | 0 | strbuf_addf(&sb, ",%s=", promisor_field_url); |
527 | 0 | strbuf_addstr_urlencode(&sb, p->url, allow_unsanitized); |
528 | |
|
529 | 0 | if (p->filter) { |
530 | 0 | strbuf_addf(&sb, ",%s=", promisor_field_filter); |
531 | 0 | strbuf_addstr_urlencode(&sb, p->filter, allow_unsanitized); |
532 | 0 | } |
533 | 0 | if (p->token) { |
534 | 0 | strbuf_addf(&sb, ",%s=", promisor_field_token); |
535 | 0 | strbuf_addstr_urlencode(&sb, p->token, allow_unsanitized); |
536 | 0 | } |
537 | 0 | } |
538 | |
|
539 | 0 | promisor_info_list_clear(&config_info); |
540 | |
|
541 | 0 | return strbuf_detach(&sb, NULL); |
542 | 0 | } |
543 | | |
544 | | enum accept_promisor { |
545 | | ACCEPT_NONE = 0, |
546 | | ACCEPT_KNOWN_URL, |
547 | | ACCEPT_KNOWN_NAME, |
548 | | ACCEPT_ALL |
549 | | }; |
550 | | |
551 | | static int match_field_against_config(const char *field, const char *value, |
552 | | struct promisor_info *config_info) |
553 | 0 | { |
554 | 0 | if (config_info->filter && !strcasecmp(field, promisor_field_filter)) |
555 | 0 | return !strcmp(config_info->filter, value); |
556 | 0 | else if (config_info->token && !strcasecmp(field, promisor_field_token)) |
557 | 0 | return !strcmp(config_info->token, value); |
558 | | |
559 | 0 | return 0; |
560 | 0 | } |
561 | | |
562 | | static int all_fields_match(struct promisor_info *advertised, |
563 | | struct string_list *config_info, |
564 | | int in_list) |
565 | 0 | { |
566 | 0 | struct string_list *fields = fields_checked(); |
567 | 0 | struct string_list_item *item_checked; |
568 | |
|
569 | 0 | for_each_string_list_item(item_checked, fields) { |
570 | 0 | int match = 0; |
571 | 0 | const char *field = item_checked->string; |
572 | 0 | const char *value = NULL; |
573 | 0 | struct string_list_item *item; |
574 | |
|
575 | 0 | if (!strcasecmp(field, promisor_field_filter)) |
576 | 0 | value = advertised->filter; |
577 | 0 | else if (!strcasecmp(field, promisor_field_token)) |
578 | 0 | value = advertised->token; |
579 | |
|
580 | 0 | if (!value) |
581 | 0 | return 0; |
582 | | |
583 | 0 | if (in_list) { |
584 | 0 | for_each_string_list_item(item, config_info) { |
585 | 0 | struct promisor_info *p = item->util; |
586 | 0 | if (match_field_against_config(field, value, p)) { |
587 | 0 | match = 1; |
588 | 0 | break; |
589 | 0 | } |
590 | 0 | } |
591 | 0 | } else { |
592 | 0 | item = string_list_lookup(config_info, advertised->name); |
593 | 0 | if (item) { |
594 | 0 | struct promisor_info *p = item->util; |
595 | 0 | match = match_field_against_config(field, value, p); |
596 | 0 | } |
597 | 0 | } |
598 | |
|
599 | 0 | if (!match) |
600 | 0 | return 0; |
601 | 0 | } |
602 | | |
603 | 0 | return 1; |
604 | 0 | } |
605 | | |
606 | | static int should_accept_remote(enum accept_promisor accept, |
607 | | struct promisor_info *advertised, |
608 | | struct string_list *config_info) |
609 | 0 | { |
610 | 0 | struct promisor_info *p; |
611 | 0 | struct string_list_item *item; |
612 | 0 | const char *remote_name = advertised->name; |
613 | 0 | const char *remote_url = advertised->url; |
614 | |
|
615 | 0 | if (accept == ACCEPT_ALL) |
616 | 0 | return all_fields_match(advertised, config_info, 1); |
617 | | |
618 | | /* Get config info for that promisor remote */ |
619 | 0 | item = string_list_lookup(config_info, remote_name); |
620 | |
|
621 | 0 | if (!item) |
622 | | /* We don't know about that remote */ |
623 | 0 | return 0; |
624 | | |
625 | 0 | p = item->util; |
626 | |
|
627 | 0 | if (accept == ACCEPT_KNOWN_NAME) |
628 | 0 | return all_fields_match(advertised, config_info, 0); |
629 | | |
630 | 0 | if (accept != ACCEPT_KNOWN_URL) |
631 | 0 | BUG("Unhandled 'enum accept_promisor' value '%d'", accept); |
632 | | |
633 | 0 | if (!remote_url || !*remote_url) { |
634 | 0 | warning(_("no or empty URL advertised for remote '%s'"), remote_name); |
635 | 0 | return 0; |
636 | 0 | } |
637 | | |
638 | 0 | if (!strcmp(p->url, remote_url)) |
639 | 0 | return all_fields_match(advertised, config_info, 0); |
640 | | |
641 | 0 | warning(_("known remote named '%s' but with URL '%s' instead of '%s'"), |
642 | 0 | remote_name, p->url, remote_url); |
643 | |
|
644 | 0 | return 0; |
645 | 0 | } |
646 | | |
647 | | static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value) |
648 | 0 | { |
649 | 0 | const char *p; |
650 | 0 | if (!skip_prefix(elem, field_name, &p) || *p != '=') |
651 | 0 | return 0; |
652 | 0 | *value = p + 1; |
653 | 0 | return 1; |
654 | 0 | } |
655 | | |
656 | | static struct promisor_info *parse_one_advertised_remote(const char *remote_info) |
657 | 0 | { |
658 | 0 | struct promisor_info *info = xcalloc(1, sizeof(*info)); |
659 | 0 | struct string_list elem_list = STRING_LIST_INIT_DUP; |
660 | 0 | struct string_list_item *item; |
661 | |
|
662 | 0 | string_list_split(&elem_list, remote_info, ",", -1); |
663 | |
|
664 | 0 | for_each_string_list_item(item, &elem_list) { |
665 | 0 | const char *elem = item->string; |
666 | 0 | const char *p = strchr(elem, '='); |
667 | |
|
668 | 0 | if (!p) { |
669 | 0 | warning(_("invalid element '%s' from remote info"), elem); |
670 | 0 | continue; |
671 | 0 | } |
672 | | |
673 | 0 | if (skip_field_name_prefix(elem, promisor_field_name, &p)) |
674 | 0 | info->name = url_percent_decode(p); |
675 | 0 | else if (skip_field_name_prefix(elem, promisor_field_url, &p)) |
676 | 0 | info->url = url_percent_decode(p); |
677 | 0 | else if (skip_field_name_prefix(elem, promisor_field_filter, &p)) |
678 | 0 | info->filter = url_percent_decode(p); |
679 | 0 | else if (skip_field_name_prefix(elem, promisor_field_token, &p)) |
680 | 0 | info->token = url_percent_decode(p); |
681 | 0 | } |
682 | |
|
683 | 0 | string_list_clear(&elem_list, 0); |
684 | |
|
685 | 0 | if (!info->name || !info->url) { |
686 | 0 | warning(_("server advertised a promisor remote without a name or URL: %s"), |
687 | 0 | remote_info); |
688 | 0 | promisor_info_free(info); |
689 | 0 | return NULL; |
690 | 0 | } |
691 | | |
692 | 0 | return info; |
693 | 0 | } |
694 | | |
695 | | static void filter_promisor_remote(struct repository *repo, |
696 | | struct strvec *accepted, |
697 | | const char *info) |
698 | 0 | { |
699 | 0 | const char *accept_str; |
700 | 0 | enum accept_promisor accept = ACCEPT_NONE; |
701 | 0 | struct string_list config_info = STRING_LIST_INIT_NODUP; |
702 | 0 | struct string_list remote_info = STRING_LIST_INIT_DUP; |
703 | 0 | struct string_list_item *item; |
704 | |
|
705 | 0 | if (!repo_config_get_string_tmp(the_repository, "promisor.acceptfromserver", &accept_str)) { |
706 | 0 | if (!*accept_str || !strcasecmp("None", accept_str)) |
707 | 0 | accept = ACCEPT_NONE; |
708 | 0 | else if (!strcasecmp("KnownUrl", accept_str)) |
709 | 0 | accept = ACCEPT_KNOWN_URL; |
710 | 0 | else if (!strcasecmp("KnownName", accept_str)) |
711 | 0 | accept = ACCEPT_KNOWN_NAME; |
712 | 0 | else if (!strcasecmp("All", accept_str)) |
713 | 0 | accept = ACCEPT_ALL; |
714 | 0 | else |
715 | 0 | warning(_("unknown '%s' value for '%s' config option"), |
716 | 0 | accept_str, "promisor.acceptfromserver"); |
717 | 0 | } |
718 | |
|
719 | 0 | if (accept == ACCEPT_NONE) |
720 | 0 | return; |
721 | | |
722 | | /* Parse remote info received */ |
723 | | |
724 | 0 | string_list_split(&remote_info, info, ";", -1); |
725 | |
|
726 | 0 | for_each_string_list_item(item, &remote_info) { |
727 | 0 | struct promisor_info *advertised; |
728 | |
|
729 | 0 | advertised = parse_one_advertised_remote(item->string); |
730 | |
|
731 | 0 | if (!advertised) |
732 | 0 | continue; |
733 | | |
734 | 0 | if (!config_info.nr) { |
735 | 0 | promisor_config_info_list(repo, &config_info, fields_checked()); |
736 | 0 | string_list_sort(&config_info); |
737 | 0 | } |
738 | |
|
739 | 0 | if (should_accept_remote(accept, advertised, &config_info)) |
740 | 0 | strvec_push(accepted, advertised->name); |
741 | |
|
742 | 0 | promisor_info_free(advertised); |
743 | 0 | } |
744 | |
|
745 | 0 | promisor_info_list_clear(&config_info); |
746 | 0 | string_list_clear(&remote_info, 0); |
747 | 0 | } |
748 | | |
749 | | char *promisor_remote_reply(const char *info) |
750 | 0 | { |
751 | 0 | struct strvec accepted = STRVEC_INIT; |
752 | 0 | struct strbuf reply = STRBUF_INIT; |
753 | |
|
754 | 0 | filter_promisor_remote(the_repository, &accepted, info); |
755 | |
|
756 | 0 | if (!accepted.nr) |
757 | 0 | return NULL; |
758 | | |
759 | 0 | for (size_t i = 0; i < accepted.nr; i++) { |
760 | 0 | if (i) |
761 | 0 | strbuf_addch(&reply, ';'); |
762 | 0 | strbuf_addstr_urlencode(&reply, accepted.v[i], allow_unsanitized); |
763 | 0 | } |
764 | |
|
765 | 0 | strvec_clear(&accepted); |
766 | |
|
767 | 0 | return strbuf_detach(&reply, NULL); |
768 | 0 | } |
769 | | |
770 | | void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes) |
771 | 0 | { |
772 | 0 | struct string_list accepted_remotes = STRING_LIST_INIT_DUP; |
773 | 0 | struct string_list_item *item; |
774 | |
|
775 | 0 | string_list_split(&accepted_remotes, remotes, ";", -1); |
776 | |
|
777 | 0 | for_each_string_list_item(item, &accepted_remotes) { |
778 | 0 | char *decoded_remote = url_percent_decode(item->string); |
779 | 0 | struct promisor_remote *p = repo_promisor_remote_find(r, decoded_remote); |
780 | |
|
781 | 0 | if (p) |
782 | 0 | p->accepted = 1; |
783 | 0 | else |
784 | 0 | warning(_("accepted promisor remote '%s' not found"), |
785 | 0 | decoded_remote); |
786 | |
|
787 | 0 | free(decoded_remote); |
788 | 0 | } |
789 | |
|
790 | 0 | string_list_clear(&accepted_remotes, 0); |
791 | 0 | } |