/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 | free(r->advertised_filter); |
197 | 0 | config->promisors = config->promisors->next; |
198 | 0 | free(r); |
199 | 0 | } |
200 | |
|
201 | 0 | config->promisors_tail = &config->promisors; |
202 | 0 | } |
203 | | |
204 | | void repo_promisor_remote_reinit(struct repository *r) |
205 | 0 | { |
206 | 0 | promisor_remote_clear(r->promisor_remote_config); |
207 | 0 | FREE_AND_NULL(r->promisor_remote_config); |
208 | 0 | promisor_remote_init(r); |
209 | 0 | } |
210 | | |
211 | | struct promisor_remote *repo_promisor_remote_find(struct repository *r, |
212 | | const char *remote_name) |
213 | 0 | { |
214 | 0 | promisor_remote_init(r); |
215 | |
|
216 | 0 | if (!remote_name) |
217 | 0 | return r->promisor_remote_config->promisors; |
218 | | |
219 | 0 | return promisor_remote_lookup(r->promisor_remote_config, remote_name, NULL); |
220 | 0 | } |
221 | | |
222 | | int repo_has_promisor_remote(struct repository *r) |
223 | 0 | { |
224 | 0 | return !!repo_promisor_remote_find(r, NULL); |
225 | 0 | } |
226 | | |
227 | | int repo_has_accepted_promisor_remote(struct repository *r) |
228 | 0 | { |
229 | 0 | struct promisor_remote *p; |
230 | |
|
231 | 0 | promisor_remote_init(r); |
232 | |
|
233 | 0 | for (p = r->promisor_remote_config->promisors; p; p = p->next) |
234 | 0 | if (p->accepted) |
235 | 0 | return 1; |
236 | 0 | return 0; |
237 | 0 | } |
238 | | |
239 | | static int remove_fetched_oids(struct repository *repo, |
240 | | struct object_id **oids, |
241 | | int oid_nr, int to_free) |
242 | 0 | { |
243 | 0 | int i, remaining_nr = 0; |
244 | 0 | int *remaining = xcalloc(oid_nr, sizeof(*remaining)); |
245 | 0 | struct object_id *old_oids = *oids; |
246 | 0 | struct object_id *new_oids; |
247 | |
|
248 | 0 | for (i = 0; i < oid_nr; i++) |
249 | 0 | if (odb_read_object_info_extended(repo->objects, &old_oids[i], NULL, |
250 | 0 | OBJECT_INFO_SKIP_FETCH_OBJECT)) { |
251 | 0 | remaining[i] = 1; |
252 | 0 | remaining_nr++; |
253 | 0 | } |
254 | |
|
255 | 0 | if (remaining_nr) { |
256 | 0 | int j = 0; |
257 | 0 | CALLOC_ARRAY(new_oids, remaining_nr); |
258 | 0 | for (i = 0; i < oid_nr; i++) |
259 | 0 | if (remaining[i]) |
260 | 0 | oidcpy(&new_oids[j++], &old_oids[i]); |
261 | 0 | *oids = new_oids; |
262 | 0 | if (to_free) |
263 | 0 | free(old_oids); |
264 | 0 | } |
265 | |
|
266 | 0 | free(remaining); |
267 | |
|
268 | 0 | return remaining_nr; |
269 | 0 | } |
270 | | |
271 | | void promisor_remote_get_direct(struct repository *repo, |
272 | | const struct object_id *oids, |
273 | | int oid_nr) |
274 | 0 | { |
275 | 0 | struct promisor_remote *r; |
276 | 0 | struct object_id *remaining_oids = (struct object_id *)oids; |
277 | 0 | int remaining_nr = oid_nr; |
278 | 0 | int to_free = 0; |
279 | 0 | int i; |
280 | |
|
281 | 0 | if (oid_nr == 0) |
282 | 0 | return; |
283 | | |
284 | 0 | promisor_remote_init(repo); |
285 | |
|
286 | 0 | for (r = repo->promisor_remote_config->promisors; r; r = r->next) { |
287 | 0 | if (fetch_objects(repo, r->name, remaining_oids, remaining_nr) < 0) { |
288 | 0 | if (remaining_nr == 1) |
289 | 0 | continue; |
290 | 0 | remaining_nr = remove_fetched_oids(repo, &remaining_oids, |
291 | 0 | remaining_nr, to_free); |
292 | 0 | if (remaining_nr) { |
293 | 0 | to_free = 1; |
294 | 0 | continue; |
295 | 0 | } |
296 | 0 | } |
297 | 0 | goto all_fetched; |
298 | 0 | } |
299 | | |
300 | 0 | for (i = 0; i < remaining_nr; i++) { |
301 | 0 | if (is_promisor_object(repo, &remaining_oids[i])) |
302 | 0 | die(_("could not fetch %s from promisor remote"), |
303 | 0 | oid_to_hex(&remaining_oids[i])); |
304 | 0 | } |
305 | | |
306 | 0 | all_fetched: |
307 | 0 | if (to_free) |
308 | 0 | free(remaining_oids); |
309 | 0 | } |
310 | | |
311 | | static int allow_unsanitized(char ch) |
312 | 0 | { |
313 | 0 | if (ch == ',' || ch == ';' || ch == '%') |
314 | 0 | return 0; |
315 | 0 | return ch > 32 && ch < 127; |
316 | 0 | } |
317 | | |
318 | | /* |
319 | | * All the fields used in "promisor-remote" protocol capability, |
320 | | * including the mandatory "name" and "url" ones. |
321 | | */ |
322 | | static const char promisor_field_name[] = "name"; |
323 | | static const char promisor_field_url[] = "url"; |
324 | | static const char promisor_field_filter[] = "partialCloneFilter"; |
325 | | static const char promisor_field_token[] = "token"; |
326 | | |
327 | | /* |
328 | | * List of optional field names that can be used in the |
329 | | * "promisor-remote" protocol capability (others must be |
330 | | * ignored). Each field should correspond to a configurable property |
331 | | * of a remote that can be relevant for the client. |
332 | | */ |
333 | | static const char *known_fields[] = { |
334 | | promisor_field_filter, /* Filter used for partial clone */ |
335 | | promisor_field_token, /* Authentication token for the remote */ |
336 | | NULL |
337 | | }; |
338 | | |
339 | | /* |
340 | | * Check if 'field' is in the list of the known field names for the |
341 | | * "promisor-remote" protocol capability. |
342 | | */ |
343 | | static int is_known_field(const char *field) |
344 | 0 | { |
345 | 0 | const char **p; |
346 | |
|
347 | 0 | for (p = known_fields; *p; p++) |
348 | 0 | if (!strcasecmp(*p, field)) |
349 | 0 | return 1; |
350 | 0 | return 0; |
351 | 0 | } |
352 | | |
353 | | static int is_valid_field(struct string_list_item *item, void *cb_data) |
354 | 0 | { |
355 | 0 | const char *field = item->string; |
356 | 0 | const char *config_key = (const char *)cb_data; |
357 | |
|
358 | 0 | if (!is_known_field(field)) { |
359 | 0 | warning(_("unsupported field '%s' in '%s' config"), field, config_key); |
360 | 0 | return 0; |
361 | 0 | } |
362 | 0 | return 1; |
363 | 0 | } |
364 | | |
365 | | static char *fields_from_config(struct string_list *fields_list, const char *config_key) |
366 | 0 | { |
367 | 0 | char *fields = NULL; |
368 | |
|
369 | 0 | if (!repo_config_get_string(the_repository, config_key, &fields) && *fields) { |
370 | 0 | string_list_split_in_place_f(fields_list, fields, ",", -1, |
371 | 0 | STRING_LIST_SPLIT_TRIM | |
372 | 0 | STRING_LIST_SPLIT_NONEMPTY); |
373 | 0 | filter_string_list(fields_list, 0, is_valid_field, (void *)config_key); |
374 | 0 | } |
375 | |
|
376 | 0 | return fields; |
377 | 0 | } |
378 | | |
379 | | static struct string_list *initialize_fields_list(struct string_list *fields_list, int *initialized, |
380 | | const char *config_key) |
381 | 0 | { |
382 | 0 | if (!*initialized) { |
383 | 0 | fields_list->cmp = strcasecmp; |
384 | 0 | fields_from_config(fields_list, config_key); |
385 | 0 | *initialized = 1; |
386 | 0 | } |
387 | |
|
388 | 0 | return fields_list; |
389 | 0 | } |
390 | | |
391 | | static struct string_list *fields_sent(void) |
392 | 0 | { |
393 | 0 | static struct string_list fields_list = STRING_LIST_INIT_NODUP; |
394 | 0 | static int initialized; |
395 | |
|
396 | 0 | return initialize_fields_list(&fields_list, &initialized, "promisor.sendFields"); |
397 | 0 | } |
398 | | |
399 | | static struct string_list *fields_checked(void) |
400 | 0 | { |
401 | 0 | static struct string_list fields_list = STRING_LIST_INIT_NODUP; |
402 | 0 | static int initialized; |
403 | |
|
404 | 0 | return initialize_fields_list(&fields_list, &initialized, "promisor.checkFields"); |
405 | 0 | } |
406 | | |
407 | | static struct string_list *fields_stored(void) |
408 | 0 | { |
409 | 0 | static struct string_list fields_list = STRING_LIST_INIT_NODUP; |
410 | 0 | static int initialized; |
411 | |
|
412 | 0 | return initialize_fields_list(&fields_list, &initialized, "promisor.storeFields"); |
413 | 0 | } |
414 | | |
415 | | /* |
416 | | * Struct for promisor remotes involved in the "promisor-remote" |
417 | | * protocol capability. |
418 | | * |
419 | | * Except for "name", each <member> in this struct and its <value> |
420 | | * should correspond (either on the client side or on the server side) |
421 | | * to a "remote.<name>.<member>" config variable set to <value> where |
422 | | * "<name>" is a promisor remote name. |
423 | | */ |
424 | | struct promisor_info { |
425 | | const char *name; |
426 | | const char *url; |
427 | | const char *filter; |
428 | | const char *token; |
429 | | }; |
430 | | |
431 | | static void promisor_info_free(struct promisor_info *p) |
432 | 0 | { |
433 | 0 | free((char *)p->name); |
434 | 0 | free((char *)p->url); |
435 | 0 | free((char *)p->filter); |
436 | 0 | free((char *)p->token); |
437 | 0 | free(p); |
438 | 0 | } |
439 | | |
440 | | static void promisor_info_list_clear(struct string_list *list) |
441 | 0 | { |
442 | 0 | for (size_t i = 0; i < list->nr; i++) |
443 | 0 | promisor_info_free(list->items[i].util); |
444 | 0 | string_list_clear(list, 0); |
445 | 0 | } |
446 | | |
447 | | static void set_one_field(struct promisor_info *p, |
448 | | const char *field, const char *value) |
449 | 0 | { |
450 | 0 | if (!strcasecmp(field, promisor_field_filter)) |
451 | 0 | p->filter = xstrdup(value); |
452 | 0 | else if (!strcasecmp(field, promisor_field_token)) |
453 | 0 | p->token = xstrdup(value); |
454 | 0 | else |
455 | 0 | BUG("invalid field '%s'", field); |
456 | 0 | } |
457 | | |
458 | | static void set_fields(struct promisor_info *p, |
459 | | struct string_list *field_names) |
460 | 0 | { |
461 | 0 | struct string_list_item *item; |
462 | |
|
463 | 0 | for_each_string_list_item(item, field_names) { |
464 | 0 | char *key = xstrfmt("remote.%s.%s", p->name, item->string); |
465 | 0 | const char *val; |
466 | 0 | if (!repo_config_get_string_tmp(the_repository, key, &val) && *val) |
467 | 0 | set_one_field(p, item->string, val); |
468 | 0 | free(key); |
469 | 0 | } |
470 | 0 | } |
471 | | |
472 | | /* |
473 | | * Populate 'list' with promisor remote information from the config. |
474 | | * The 'util' pointer of each list item will hold a 'struct |
475 | | * promisor_info'. Except "name" and "url", only members of that |
476 | | * struct specified by the 'field_names' list are set (using values |
477 | | * from the configuration). |
478 | | */ |
479 | | static void promisor_config_info_list(struct repository *repo, |
480 | | struct string_list *list, |
481 | | struct string_list *field_names) |
482 | 0 | { |
483 | 0 | struct promisor_remote *r; |
484 | |
|
485 | 0 | promisor_remote_init(repo); |
486 | |
|
487 | 0 | for (r = repo->promisor_remote_config->promisors; r; r = r->next) { |
488 | 0 | const char *url; |
489 | 0 | char *url_key = xstrfmt("remote.%s.url", r->name); |
490 | | |
491 | | /* Only add remotes with a non empty URL */ |
492 | 0 | if (!repo_config_get_string_tmp(the_repository, url_key, &url) && *url) { |
493 | 0 | struct promisor_info *new_info = xcalloc(1, sizeof(*new_info)); |
494 | 0 | struct string_list_item *item; |
495 | |
|
496 | 0 | new_info->name = xstrdup(r->name); |
497 | 0 | new_info->url = xstrdup(url); |
498 | |
|
499 | 0 | if (field_names) |
500 | 0 | set_fields(new_info, field_names); |
501 | |
|
502 | 0 | item = string_list_append(list, new_info->name); |
503 | 0 | item->util = new_info; |
504 | 0 | } |
505 | |
|
506 | 0 | free(url_key); |
507 | 0 | } |
508 | 0 | } |
509 | | |
510 | | char *promisor_remote_info(struct repository *repo) |
511 | 0 | { |
512 | 0 | struct strbuf sb = STRBUF_INIT; |
513 | 0 | int advertise_promisors = 0; |
514 | 0 | struct string_list config_info = STRING_LIST_INIT_NODUP; |
515 | 0 | struct string_list_item *item; |
516 | |
|
517 | 0 | repo_config_get_bool(the_repository, "promisor.advertise", &advertise_promisors); |
518 | |
|
519 | 0 | if (!advertise_promisors) |
520 | 0 | return NULL; |
521 | | |
522 | 0 | promisor_config_info_list(repo, &config_info, fields_sent()); |
523 | |
|
524 | 0 | if (!config_info.nr) |
525 | 0 | return NULL; |
526 | | |
527 | 0 | for_each_string_list_item(item, &config_info) { |
528 | 0 | struct promisor_info *p = item->util; |
529 | |
|
530 | 0 | if (item != config_info.items) |
531 | 0 | strbuf_addch(&sb, ';'); |
532 | |
|
533 | 0 | strbuf_addf(&sb, "%s=", promisor_field_name); |
534 | 0 | strbuf_addstr_urlencode(&sb, p->name, allow_unsanitized); |
535 | 0 | strbuf_addf(&sb, ",%s=", promisor_field_url); |
536 | 0 | strbuf_addstr_urlencode(&sb, p->url, allow_unsanitized); |
537 | |
|
538 | 0 | if (p->filter) { |
539 | 0 | strbuf_addf(&sb, ",%s=", promisor_field_filter); |
540 | 0 | strbuf_addstr_urlencode(&sb, p->filter, allow_unsanitized); |
541 | 0 | } |
542 | 0 | if (p->token) { |
543 | 0 | strbuf_addf(&sb, ",%s=", promisor_field_token); |
544 | 0 | strbuf_addstr_urlencode(&sb, p->token, allow_unsanitized); |
545 | 0 | } |
546 | 0 | } |
547 | |
|
548 | 0 | promisor_info_list_clear(&config_info); |
549 | |
|
550 | 0 | return strbuf_detach(&sb, NULL); |
551 | 0 | } |
552 | | |
553 | | enum accept_promisor { |
554 | | ACCEPT_NONE = 0, |
555 | | ACCEPT_KNOWN_URL, |
556 | | ACCEPT_KNOWN_NAME, |
557 | | ACCEPT_ALL |
558 | | }; |
559 | | |
560 | | static int match_field_against_config(const char *field, const char *value, |
561 | | struct promisor_info *config_info) |
562 | 0 | { |
563 | 0 | if (config_info->filter && !strcasecmp(field, promisor_field_filter)) |
564 | 0 | return !strcmp(config_info->filter, value); |
565 | 0 | else if (config_info->token && !strcasecmp(field, promisor_field_token)) |
566 | 0 | return !strcmp(config_info->token, value); |
567 | | |
568 | 0 | return 0; |
569 | 0 | } |
570 | | |
571 | | static int all_fields_match(struct promisor_info *advertised, |
572 | | struct string_list *config_info, |
573 | | int in_list) |
574 | 0 | { |
575 | 0 | struct string_list *fields = fields_checked(); |
576 | 0 | struct string_list_item *item_checked; |
577 | |
|
578 | 0 | for_each_string_list_item(item_checked, fields) { |
579 | 0 | int match = 0; |
580 | 0 | const char *field = item_checked->string; |
581 | 0 | const char *value = NULL; |
582 | 0 | struct string_list_item *item; |
583 | |
|
584 | 0 | if (!strcasecmp(field, promisor_field_filter)) |
585 | 0 | value = advertised->filter; |
586 | 0 | else if (!strcasecmp(field, promisor_field_token)) |
587 | 0 | value = advertised->token; |
588 | |
|
589 | 0 | if (!value) |
590 | 0 | return 0; |
591 | | |
592 | 0 | if (in_list) { |
593 | 0 | for_each_string_list_item(item, config_info) { |
594 | 0 | struct promisor_info *p = item->util; |
595 | 0 | if (match_field_against_config(field, value, p)) { |
596 | 0 | match = 1; |
597 | 0 | break; |
598 | 0 | } |
599 | 0 | } |
600 | 0 | } else { |
601 | 0 | item = string_list_lookup(config_info, advertised->name); |
602 | 0 | if (item) { |
603 | 0 | struct promisor_info *p = item->util; |
604 | 0 | match = match_field_against_config(field, value, p); |
605 | 0 | } |
606 | 0 | } |
607 | |
|
608 | 0 | if (!match) |
609 | 0 | return 0; |
610 | 0 | } |
611 | | |
612 | 0 | return 1; |
613 | 0 | } |
614 | | |
615 | | static int should_accept_remote(enum accept_promisor accept, |
616 | | struct promisor_info *advertised, |
617 | | struct string_list *config_info) |
618 | 0 | { |
619 | 0 | struct promisor_info *p; |
620 | 0 | struct string_list_item *item; |
621 | 0 | const char *remote_name = advertised->name; |
622 | 0 | const char *remote_url = advertised->url; |
623 | |
|
624 | 0 | if (accept == ACCEPT_ALL) |
625 | 0 | return all_fields_match(advertised, config_info, 1); |
626 | | |
627 | | /* Get config info for that promisor remote */ |
628 | 0 | item = string_list_lookup(config_info, remote_name); |
629 | |
|
630 | 0 | if (!item) |
631 | | /* We don't know about that remote */ |
632 | 0 | return 0; |
633 | | |
634 | 0 | p = item->util; |
635 | |
|
636 | 0 | if (accept == ACCEPT_KNOWN_NAME) |
637 | 0 | return all_fields_match(advertised, config_info, 0); |
638 | | |
639 | 0 | if (accept != ACCEPT_KNOWN_URL) |
640 | 0 | BUG("Unhandled 'enum accept_promisor' value '%d'", accept); |
641 | | |
642 | 0 | if (!remote_url || !*remote_url) { |
643 | 0 | warning(_("no or empty URL advertised for remote '%s'"), remote_name); |
644 | 0 | return 0; |
645 | 0 | } |
646 | | |
647 | 0 | if (!strcmp(p->url, remote_url)) |
648 | 0 | return all_fields_match(advertised, config_info, 0); |
649 | | |
650 | 0 | warning(_("known remote named '%s' but with URL '%s' instead of '%s'"), |
651 | 0 | remote_name, p->url, remote_url); |
652 | |
|
653 | 0 | return 0; |
654 | 0 | } |
655 | | |
656 | | static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value) |
657 | 0 | { |
658 | 0 | const char *p; |
659 | 0 | if (!skip_prefix(elem, field_name, &p) || *p != '=') |
660 | 0 | return 0; |
661 | 0 | *value = p + 1; |
662 | 0 | return 1; |
663 | 0 | } |
664 | | |
665 | | static struct promisor_info *parse_one_advertised_remote(const char *remote_info) |
666 | 0 | { |
667 | 0 | struct promisor_info *info = xcalloc(1, sizeof(*info)); |
668 | 0 | struct string_list elem_list = STRING_LIST_INIT_DUP; |
669 | 0 | struct string_list_item *item; |
670 | |
|
671 | 0 | string_list_split(&elem_list, remote_info, ",", -1); |
672 | |
|
673 | 0 | for_each_string_list_item(item, &elem_list) { |
674 | 0 | const char *elem = item->string; |
675 | 0 | const char *p = strchr(elem, '='); |
676 | |
|
677 | 0 | if (!p) { |
678 | 0 | warning(_("invalid element '%s' from remote info"), elem); |
679 | 0 | continue; |
680 | 0 | } |
681 | | |
682 | 0 | if (skip_field_name_prefix(elem, promisor_field_name, &p)) |
683 | 0 | info->name = url_percent_decode(p); |
684 | 0 | else if (skip_field_name_prefix(elem, promisor_field_url, &p)) |
685 | 0 | info->url = url_percent_decode(p); |
686 | 0 | else if (skip_field_name_prefix(elem, promisor_field_filter, &p)) |
687 | 0 | info->filter = url_percent_decode(p); |
688 | 0 | else if (skip_field_name_prefix(elem, promisor_field_token, &p)) |
689 | 0 | info->token = url_percent_decode(p); |
690 | 0 | } |
691 | |
|
692 | 0 | string_list_clear(&elem_list, 0); |
693 | |
|
694 | 0 | if (!info->name || !info->url) { |
695 | 0 | warning(_("server advertised a promisor remote without a name or URL: %s"), |
696 | 0 | remote_info); |
697 | 0 | promisor_info_free(info); |
698 | 0 | return NULL; |
699 | 0 | } |
700 | | |
701 | 0 | return info; |
702 | 0 | } |
703 | | |
704 | | static bool store_one_field(struct repository *repo, const char *remote_name, |
705 | | const char *field_name, const char *field_key, |
706 | | const char *advertised, const char *current) |
707 | 0 | { |
708 | 0 | if (advertised && (!current || strcmp(current, advertised))) { |
709 | 0 | char *key = xstrfmt("remote.%s.%s", remote_name, field_key); |
710 | |
|
711 | 0 | fprintf(stderr, _("Storing new %s from server for remote '%s'.\n" |
712 | 0 | " '%s' -> '%s'\n"), |
713 | 0 | field_name, remote_name, |
714 | 0 | current ? current : "", |
715 | 0 | advertised); |
716 | |
|
717 | 0 | repo_config_set_gently(repo, key, advertised); |
718 | 0 | free(key); |
719 | |
|
720 | 0 | return true; |
721 | 0 | } |
722 | | |
723 | 0 | return false; |
724 | 0 | } |
725 | | |
726 | | /* Check that a filter is valid by parsing it */ |
727 | | static bool valid_filter(const char *filter, const char *remote_name) |
728 | 0 | { |
729 | 0 | struct list_objects_filter_options filter_opts = LIST_OBJECTS_FILTER_INIT; |
730 | 0 | struct strbuf err = STRBUF_INIT; |
731 | 0 | int res = gently_parse_list_objects_filter(&filter_opts, filter, &err); |
732 | |
|
733 | 0 | if (res) |
734 | 0 | warning(_("invalid filter '%s' for remote '%s' " |
735 | 0 | "will not be stored: %s"), |
736 | 0 | filter, remote_name, err.buf); |
737 | |
|
738 | 0 | list_objects_filter_release(&filter_opts); |
739 | 0 | strbuf_release(&err); |
740 | |
|
741 | 0 | return !res; |
742 | 0 | } |
743 | | |
744 | | /* Check that a token doesn't contain any control character */ |
745 | | static bool valid_token(const char *token, const char *remote_name) |
746 | 0 | { |
747 | 0 | const char *c = token; |
748 | |
|
749 | 0 | for (; *c; c++) |
750 | 0 | if (iscntrl(*c)) { |
751 | 0 | warning(_("invalid token '%s' for remote '%s' " |
752 | 0 | "will not be stored"), |
753 | 0 | token, remote_name); |
754 | 0 | return false; |
755 | 0 | } |
756 | | |
757 | 0 | return true; |
758 | 0 | } |
759 | | |
760 | | struct store_info { |
761 | | struct repository *repo; |
762 | | struct string_list config_info; |
763 | | bool store_filter; |
764 | | bool store_token; |
765 | | }; |
766 | | |
767 | | static struct store_info *store_info_new(struct repository *repo) |
768 | 0 | { |
769 | 0 | struct string_list *fields_to_store = fields_stored(); |
770 | 0 | struct store_info *s = xmalloc(sizeof(*s)); |
771 | |
|
772 | 0 | s->repo = repo; |
773 | |
|
774 | 0 | string_list_init_nodup(&s->config_info); |
775 | 0 | promisor_config_info_list(repo, &s->config_info, fields_to_store); |
776 | 0 | string_list_sort(&s->config_info); |
777 | |
|
778 | 0 | s->store_filter = !!string_list_lookup(fields_to_store, promisor_field_filter); |
779 | 0 | s->store_token = !!string_list_lookup(fields_to_store, promisor_field_token); |
780 | |
|
781 | 0 | return s; |
782 | 0 | } |
783 | | |
784 | | static void store_info_free(struct store_info *s) |
785 | 0 | { |
786 | 0 | if (s) { |
787 | 0 | promisor_info_list_clear(&s->config_info); |
788 | 0 | free(s); |
789 | 0 | } |
790 | 0 | } |
791 | | |
792 | | static bool promisor_store_advertised_fields(struct promisor_info *advertised, |
793 | | struct store_info *store_info) |
794 | 0 | { |
795 | 0 | struct promisor_info *p; |
796 | 0 | struct string_list_item *item; |
797 | 0 | const char *remote_name = advertised->name; |
798 | 0 | bool reload_config = false; |
799 | |
|
800 | 0 | if (!(store_info->store_filter || store_info->store_token)) |
801 | 0 | return false; |
802 | | |
803 | | /* |
804 | | * Get existing config info for the advertised promisor |
805 | | * remote. This ensures the remote is already configured on |
806 | | * the client side. |
807 | | */ |
808 | 0 | item = string_list_lookup(&store_info->config_info, remote_name); |
809 | |
|
810 | 0 | if (!item) |
811 | 0 | return false; |
812 | | |
813 | 0 | p = item->util; |
814 | |
|
815 | 0 | if (store_info->store_filter && advertised->filter && |
816 | 0 | valid_filter(advertised->filter, remote_name)) |
817 | 0 | reload_config |= store_one_field(store_info->repo, remote_name, |
818 | 0 | "filter", promisor_field_filter, |
819 | 0 | advertised->filter, p->filter); |
820 | |
|
821 | 0 | if (store_info->store_token && advertised->token && |
822 | 0 | valid_token(advertised->token, remote_name)) |
823 | 0 | reload_config |= store_one_field(store_info->repo, remote_name, |
824 | 0 | "token", promisor_field_token, |
825 | 0 | advertised->token, p->token); |
826 | |
|
827 | 0 | return reload_config; |
828 | 0 | } |
829 | | |
830 | | static void filter_promisor_remote(struct repository *repo, |
831 | | struct strvec *accepted, |
832 | | const char *info) |
833 | 0 | { |
834 | 0 | const char *accept_str; |
835 | 0 | enum accept_promisor accept = ACCEPT_NONE; |
836 | 0 | struct string_list config_info = STRING_LIST_INIT_NODUP; |
837 | 0 | struct string_list remote_info = STRING_LIST_INIT_DUP; |
838 | 0 | struct store_info *store_info = NULL; |
839 | 0 | struct string_list_item *item; |
840 | 0 | bool reload_config = false; |
841 | 0 | struct string_list accepted_filters = STRING_LIST_INIT_DUP; |
842 | |
|
843 | 0 | if (!repo_config_get_string_tmp(the_repository, "promisor.acceptfromserver", &accept_str)) { |
844 | 0 | if (!*accept_str || !strcasecmp("None", accept_str)) |
845 | 0 | accept = ACCEPT_NONE; |
846 | 0 | else if (!strcasecmp("KnownUrl", accept_str)) |
847 | 0 | accept = ACCEPT_KNOWN_URL; |
848 | 0 | else if (!strcasecmp("KnownName", accept_str)) |
849 | 0 | accept = ACCEPT_KNOWN_NAME; |
850 | 0 | else if (!strcasecmp("All", accept_str)) |
851 | 0 | accept = ACCEPT_ALL; |
852 | 0 | else |
853 | 0 | warning(_("unknown '%s' value for '%s' config option"), |
854 | 0 | accept_str, "promisor.acceptfromserver"); |
855 | 0 | } |
856 | |
|
857 | 0 | if (accept == ACCEPT_NONE) |
858 | 0 | return; |
859 | | |
860 | | /* Parse remote info received */ |
861 | | |
862 | 0 | string_list_split(&remote_info, info, ";", -1); |
863 | |
|
864 | 0 | for_each_string_list_item(item, &remote_info) { |
865 | 0 | struct promisor_info *advertised; |
866 | |
|
867 | 0 | advertised = parse_one_advertised_remote(item->string); |
868 | |
|
869 | 0 | if (!advertised) |
870 | 0 | continue; |
871 | | |
872 | 0 | if (!config_info.nr) { |
873 | 0 | promisor_config_info_list(repo, &config_info, fields_checked()); |
874 | 0 | string_list_sort(&config_info); |
875 | 0 | } |
876 | |
|
877 | 0 | if (should_accept_remote(accept, advertised, &config_info)) { |
878 | 0 | if (!store_info) |
879 | 0 | store_info = store_info_new(repo); |
880 | 0 | if (promisor_store_advertised_fields(advertised, store_info)) |
881 | 0 | reload_config = true; |
882 | |
|
883 | 0 | strvec_push(accepted, advertised->name); |
884 | | |
885 | | /* Capture advertised filters for accepted remotes */ |
886 | 0 | if (advertised->filter) { |
887 | 0 | struct string_list_item *i; |
888 | 0 | i = string_list_append(&accepted_filters, advertised->name); |
889 | 0 | i->util = xstrdup(advertised->filter); |
890 | 0 | } |
891 | 0 | } |
892 | |
|
893 | 0 | promisor_info_free(advertised); |
894 | 0 | } |
895 | |
|
896 | 0 | promisor_info_list_clear(&config_info); |
897 | 0 | string_list_clear(&remote_info, 0); |
898 | 0 | store_info_free(store_info); |
899 | |
|
900 | 0 | if (reload_config) |
901 | 0 | repo_promisor_remote_reinit(repo); |
902 | | |
903 | | /* Apply accepted remote filters to the stable repo state */ |
904 | 0 | for_each_string_list_item(item, &accepted_filters) { |
905 | 0 | struct promisor_remote *r = repo_promisor_remote_find(repo, item->string); |
906 | 0 | if (r) { |
907 | 0 | free(r->advertised_filter); |
908 | 0 | r->advertised_filter = item->util; |
909 | 0 | item->util = NULL; |
910 | 0 | } |
911 | 0 | } |
912 | |
|
913 | 0 | string_list_clear(&accepted_filters, 1); |
914 | | |
915 | | /* Mark the remotes as accepted in the repository state */ |
916 | 0 | for (size_t i = 0; i < accepted->nr; i++) { |
917 | 0 | struct promisor_remote *r = repo_promisor_remote_find(repo, accepted->v[i]); |
918 | 0 | if (r) |
919 | 0 | r->accepted = 1; |
920 | 0 | } |
921 | 0 | } |
922 | | |
923 | | void promisor_remote_reply(const char *info, char **accepted_out) |
924 | 0 | { |
925 | 0 | struct strvec accepted = STRVEC_INIT; |
926 | |
|
927 | 0 | filter_promisor_remote(the_repository, &accepted, info); |
928 | |
|
929 | 0 | if (accepted_out) { |
930 | 0 | if (accepted.nr) { |
931 | 0 | struct strbuf reply = STRBUF_INIT; |
932 | 0 | for (size_t i = 0; i < accepted.nr; i++) { |
933 | 0 | if (i) |
934 | 0 | strbuf_addch(&reply, ';'); |
935 | 0 | strbuf_addstr_urlencode(&reply, accepted.v[i], allow_unsanitized); |
936 | 0 | } |
937 | 0 | *accepted_out = strbuf_detach(&reply, NULL); |
938 | 0 | } else { |
939 | 0 | *accepted_out = NULL; |
940 | 0 | } |
941 | 0 | } |
942 | |
|
943 | 0 | strvec_clear(&accepted); |
944 | 0 | } |
945 | | |
946 | | void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes) |
947 | 0 | { |
948 | 0 | struct string_list accepted_remotes = STRING_LIST_INIT_DUP; |
949 | 0 | struct string_list_item *item; |
950 | |
|
951 | 0 | string_list_split(&accepted_remotes, remotes, ";", -1); |
952 | |
|
953 | 0 | for_each_string_list_item(item, &accepted_remotes) { |
954 | 0 | char *decoded_remote = url_percent_decode(item->string); |
955 | 0 | struct promisor_remote *p = repo_promisor_remote_find(r, decoded_remote); |
956 | |
|
957 | 0 | if (p) |
958 | 0 | p->accepted = 1; |
959 | 0 | else |
960 | 0 | warning(_("accepted promisor remote '%s' not found"), |
961 | 0 | decoded_remote); |
962 | |
|
963 | 0 | free(decoded_remote); |
964 | 0 | } |
965 | |
|
966 | 0 | string_list_clear(&accepted_remotes, 0); |
967 | 0 | } |
968 | | |
969 | | char *promisor_remote_construct_filter(struct repository *repo) |
970 | 0 | { |
971 | 0 | struct promisor_remote *r; |
972 | 0 | struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; |
973 | 0 | struct strbuf err = STRBUF_INIT; |
974 | 0 | char *result = NULL; |
975 | |
|
976 | 0 | promisor_remote_init(repo); |
977 | |
|
978 | 0 | for (r = repo->promisor_remote_config->promisors; r; r = r->next) { |
979 | 0 | if (r->accepted && r->advertised_filter) |
980 | 0 | if (gently_parse_list_objects_filter(&filter_options, |
981 | 0 | r->advertised_filter, |
982 | 0 | &err)) { |
983 | 0 | warning(_("promisor remote '%s' advertised invalid filter '%s': %s"), |
984 | 0 | r->name, r->advertised_filter, err.buf); |
985 | 0 | strbuf_reset(&err); |
986 | 0 | continue; |
987 | 0 | } |
988 | 0 | } |
989 | |
|
990 | 0 | if (filter_options.choice) |
991 | 0 | result = xstrdup(expand_list_objects_filter_spec(&filter_options)); |
992 | |
|
993 | 0 | list_objects_filter_release(&filter_options); |
994 | 0 | strbuf_release(&err); |
995 | |
|
996 | 0 | return result; |
997 | 0 | } |