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