Merge branch 'ps/reftable-write-optim'
[git.git] / http-walker.c
blobb395ef13279eaa3f672e36512a7816fee209abf9
1 #include "git-compat-util.h"
2 #include "repository.h"
3 #include "hex.h"
4 #include "walker.h"
5 #include "http.h"
6 #include "list.h"
7 #include "transport.h"
8 #include "packfile.h"
9 #include "object-store-ll.h"
11 struct alt_base {
12 char *base;
13 int got_indices;
14 struct packed_git *packs;
15 struct alt_base *next;
18 enum object_request_state {
19 WAITING,
20 ABORTED,
21 ACTIVE,
22 COMPLETE
25 struct object_request {
26 struct walker *walker;
27 struct object_id oid;
28 struct alt_base *repo;
29 enum object_request_state state;
30 struct http_object_request *req;
31 struct list_head node;
34 struct alternates_request {
35 struct walker *walker;
36 const char *base;
37 struct strbuf *url;
38 struct strbuf *buffer;
39 struct active_request_slot *slot;
40 int http_specific;
43 struct walker_data {
44 const char *url;
45 int got_alternates;
46 struct alt_base *alt;
49 static LIST_HEAD(object_queue_head);
51 static void fetch_alternates(struct walker *walker, const char *base);
53 static void process_object_response(void *callback_data);
55 static void start_object_request(struct object_request *obj_req)
57 struct active_request_slot *slot;
58 struct http_object_request *req;
60 req = new_http_object_request(obj_req->repo->base, &obj_req->oid);
61 if (!req) {
62 obj_req->state = ABORTED;
63 return;
65 obj_req->req = req;
67 slot = req->slot;
68 slot->callback_func = process_object_response;
69 slot->callback_data = obj_req;
71 /* Try to get the request started, abort the request on error */
72 obj_req->state = ACTIVE;
73 if (!start_active_slot(slot)) {
74 obj_req->state = ABORTED;
75 release_http_object_request(req);
76 return;
80 static void finish_object_request(struct object_request *obj_req)
82 if (finish_http_object_request(obj_req->req))
83 return;
85 if (obj_req->req->rename == 0)
86 walker_say(obj_req->walker, "got %s\n", oid_to_hex(&obj_req->oid));
89 static void process_object_response(void *callback_data)
91 struct object_request *obj_req =
92 (struct object_request *)callback_data;
93 struct walker *walker = obj_req->walker;
94 struct walker_data *data = walker->data;
95 struct alt_base *alt = data->alt;
97 process_http_object_request(obj_req->req);
98 obj_req->state = COMPLETE;
100 normalize_curl_result(&obj_req->req->curl_result,
101 obj_req->req->http_code,
102 obj_req->req->errorstr,
103 sizeof(obj_req->req->errorstr));
105 /* Use alternates if necessary */
106 if (missing_target(obj_req->req)) {
107 fetch_alternates(walker, alt->base);
108 if (obj_req->repo->next) {
109 obj_req->repo =
110 obj_req->repo->next;
111 release_http_object_request(obj_req->req);
112 start_object_request(obj_req);
113 return;
117 finish_object_request(obj_req);
120 static void release_object_request(struct object_request *obj_req)
122 if (obj_req->req !=NULL && obj_req->req->localfile != -1)
123 error("fd leakage in release: %d", obj_req->req->localfile);
125 list_del(&obj_req->node);
126 free(obj_req);
129 static int fill_active_slot(void *data UNUSED)
131 struct object_request *obj_req;
132 struct list_head *pos, *tmp, *head = &object_queue_head;
134 list_for_each_safe(pos, tmp, head) {
135 obj_req = list_entry(pos, struct object_request, node);
136 if (obj_req->state == WAITING) {
137 if (repo_has_object_file(the_repository, &obj_req->oid))
138 obj_req->state = COMPLETE;
139 else {
140 start_object_request(obj_req);
141 return 1;
145 return 0;
148 static void prefetch(struct walker *walker, unsigned char *sha1)
150 struct object_request *newreq;
151 struct walker_data *data = walker->data;
153 newreq = xmalloc(sizeof(*newreq));
154 newreq->walker = walker;
155 oidread(&newreq->oid, sha1);
156 newreq->repo = data->alt;
157 newreq->state = WAITING;
158 newreq->req = NULL;
160 http_is_verbose = walker->get_verbosely;
161 list_add_tail(&newreq->node, &object_queue_head);
163 fill_active_slots();
164 step_active_slots();
167 static int is_alternate_allowed(const char *url)
169 const char *protocols[] = {
170 "http", "https", "ftp", "ftps"
172 int i;
174 if (http_follow_config != HTTP_FOLLOW_ALWAYS) {
175 warning("alternate disabled by http.followRedirects: %s", url);
176 return 0;
179 for (i = 0; i < ARRAY_SIZE(protocols); i++) {
180 const char *end;
181 if (skip_prefix(url, protocols[i], &end) &&
182 starts_with(end, "://"))
183 break;
186 if (i >= ARRAY_SIZE(protocols)) {
187 warning("ignoring alternate with unknown protocol: %s", url);
188 return 0;
190 if (!is_transport_allowed(protocols[i], 0)) {
191 warning("ignoring alternate with restricted protocol: %s", url);
192 return 0;
195 return 1;
198 static void process_alternates_response(void *callback_data)
200 struct alternates_request *alt_req =
201 (struct alternates_request *)callback_data;
202 struct walker *walker = alt_req->walker;
203 struct walker_data *cdata = walker->data;
204 struct active_request_slot *slot = alt_req->slot;
205 struct alt_base *tail = cdata->alt;
206 const char *base = alt_req->base;
207 const char null_byte = '\0';
208 char *data;
209 int i = 0;
211 normalize_curl_result(&slot->curl_result, slot->http_code,
212 curl_errorstr, sizeof(curl_errorstr));
214 if (alt_req->http_specific) {
215 if (slot->curl_result != CURLE_OK ||
216 !alt_req->buffer->len) {
218 /* Try reusing the slot to get non-http alternates */
219 alt_req->http_specific = 0;
220 strbuf_reset(alt_req->url);
221 strbuf_addf(alt_req->url, "%s/objects/info/alternates",
222 base);
223 curl_easy_setopt(slot->curl, CURLOPT_URL,
224 alt_req->url->buf);
225 active_requests++;
226 slot->in_use = 1;
227 if (slot->finished)
228 (*slot->finished) = 0;
229 if (!start_active_slot(slot)) {
230 cdata->got_alternates = -1;
231 slot->in_use = 0;
232 if (slot->finished)
233 (*slot->finished) = 1;
235 return;
237 } else if (slot->curl_result != CURLE_OK) {
238 if (!missing_target(slot)) {
239 cdata->got_alternates = -1;
240 return;
244 fwrite_buffer((char *)&null_byte, 1, 1, alt_req->buffer);
245 alt_req->buffer->len--;
246 data = alt_req->buffer->buf;
248 while (i < alt_req->buffer->len) {
249 int posn = i;
250 while (posn < alt_req->buffer->len && data[posn] != '\n')
251 posn++;
252 if (data[posn] == '\n') {
253 int okay = 0;
254 int serverlen = 0;
255 struct alt_base *newalt;
256 if (data[i] == '/') {
258 * This counts
259 * http://git.host/pub/scm/linux.git/
260 * -----------here^
261 * so memcpy(dst, base, serverlen) will
262 * copy up to "...git.host".
264 const char *colon_ss = strstr(base,"://");
265 if (colon_ss) {
266 serverlen = (strchr(colon_ss + 3, '/')
267 - base);
268 okay = 1;
270 } else if (!memcmp(data + i, "../", 3)) {
272 * Relative URL; chop the corresponding
273 * number of subpath from base (and ../
274 * from data), and concatenate the result.
276 * The code first drops ../ from data, and
277 * then drops one ../ from data and one path
278 * from base. IOW, one extra ../ is dropped
279 * from data than path is dropped from base.
281 * This is not wrong. The alternate in
282 * http://git.host/pub/scm/linux.git/
283 * to borrow from
284 * http://git.host/pub/scm/linus.git/
285 * is ../../linus.git/objects/. You need
286 * two ../../ to borrow from your direct
287 * neighbour.
289 i += 3;
290 serverlen = strlen(base);
291 while (i + 2 < posn &&
292 !memcmp(data + i, "../", 3)) {
293 do {
294 serverlen--;
295 } while (serverlen &&
296 base[serverlen - 1] != '/');
297 i += 3;
299 /* If the server got removed, give up. */
300 okay = strchr(base, ':') - base + 3 <
301 serverlen;
302 } else if (alt_req->http_specific) {
303 char *colon = strchr(data + i, ':');
304 char *slash = strchr(data + i, '/');
305 if (colon && slash && colon < data + posn &&
306 slash < data + posn && colon < slash) {
307 okay = 1;
310 if (okay) {
311 struct strbuf target = STRBUF_INIT;
312 strbuf_add(&target, base, serverlen);
313 strbuf_add(&target, data + i, posn - i);
314 if (!strbuf_strip_suffix(&target, "objects")) {
315 warning("ignoring alternate that does"
316 " not end in 'objects': %s",
317 target.buf);
318 strbuf_release(&target);
319 } else if (is_alternate_allowed(target.buf)) {
320 warning("adding alternate object store: %s",
321 target.buf);
322 newalt = xmalloc(sizeof(*newalt));
323 newalt->next = NULL;
324 newalt->base = strbuf_detach(&target, NULL);
325 newalt->got_indices = 0;
326 newalt->packs = NULL;
328 while (tail->next != NULL)
329 tail = tail->next;
330 tail->next = newalt;
331 } else {
332 strbuf_release(&target);
336 i = posn + 1;
339 cdata->got_alternates = 1;
342 static void fetch_alternates(struct walker *walker, const char *base)
344 struct strbuf buffer = STRBUF_INIT;
345 struct strbuf url = STRBUF_INIT;
346 struct active_request_slot *slot;
347 struct alternates_request alt_req;
348 struct walker_data *cdata = walker->data;
351 * If another request has already started fetching alternates,
352 * wait for them to arrive and return to processing this request's
353 * curl message
355 while (cdata->got_alternates == 0) {
356 step_active_slots();
359 /* Nothing to do if they've already been fetched */
360 if (cdata->got_alternates == 1)
361 return;
363 /* Start the fetch */
364 cdata->got_alternates = 0;
366 if (walker->get_verbosely)
367 fprintf(stderr, "Getting alternates list for %s\n", base);
369 strbuf_addf(&url, "%s/objects/info/http-alternates", base);
372 * Use a callback to process the result, since another request
373 * may fail and need to have alternates loaded before continuing
375 slot = get_active_slot();
376 slot->callback_func = process_alternates_response;
377 alt_req.walker = walker;
378 slot->callback_data = &alt_req;
380 curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &buffer);
381 curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
382 curl_easy_setopt(slot->curl, CURLOPT_URL, url.buf);
384 alt_req.base = base;
385 alt_req.url = &url;
386 alt_req.buffer = &buffer;
387 alt_req.http_specific = 1;
388 alt_req.slot = slot;
390 if (start_active_slot(slot))
391 run_active_slot(slot);
392 else
393 cdata->got_alternates = -1;
395 strbuf_release(&buffer);
396 strbuf_release(&url);
399 static int fetch_indices(struct walker *walker, struct alt_base *repo)
401 int ret;
403 if (repo->got_indices)
404 return 0;
406 if (walker->get_verbosely)
407 fprintf(stderr, "Getting pack list for %s\n", repo->base);
409 switch (http_get_info_packs(repo->base, &repo->packs)) {
410 case HTTP_OK:
411 case HTTP_MISSING_TARGET:
412 repo->got_indices = 1;
413 ret = 0;
414 break;
415 default:
416 repo->got_indices = 0;
417 ret = -1;
420 return ret;
423 static int http_fetch_pack(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
425 struct packed_git *target;
426 int ret;
427 struct slot_results results;
428 struct http_pack_request *preq;
430 if (fetch_indices(walker, repo))
431 return -1;
432 target = find_sha1_pack(sha1, repo->packs);
433 if (!target)
434 return -1;
435 close_pack_index(target);
437 if (walker->get_verbosely) {
438 fprintf(stderr, "Getting pack %s\n",
439 hash_to_hex(target->hash));
440 fprintf(stderr, " which contains %s\n",
441 hash_to_hex(sha1));
444 preq = new_http_pack_request(target->hash, repo->base);
445 if (!preq)
446 goto abort;
447 preq->slot->results = &results;
449 if (start_active_slot(preq->slot)) {
450 run_active_slot(preq->slot);
451 if (results.curl_result != CURLE_OK) {
452 error("Unable to get pack file %s\n%s", preq->url,
453 curl_errorstr);
454 goto abort;
456 } else {
457 error("Unable to start request");
458 goto abort;
461 ret = finish_http_pack_request(preq);
462 release_http_pack_request(preq);
463 if (ret)
464 return ret;
465 http_install_packfile(target, &repo->packs);
467 return 0;
469 abort:
470 return -1;
473 static void abort_object_request(struct object_request *obj_req)
475 release_object_request(obj_req);
478 static int fetch_object(struct walker *walker, unsigned char *hash)
480 char *hex = hash_to_hex(hash);
481 int ret = 0;
482 struct object_request *obj_req = NULL;
483 struct http_object_request *req;
484 struct list_head *pos, *head = &object_queue_head;
486 list_for_each(pos, head) {
487 obj_req = list_entry(pos, struct object_request, node);
488 if (hasheq(obj_req->oid.hash, hash))
489 break;
491 if (!obj_req)
492 return error("Couldn't find request for %s in the queue", hex);
494 if (repo_has_object_file(the_repository, &obj_req->oid)) {
495 if (obj_req->req)
496 abort_http_object_request(obj_req->req);
497 abort_object_request(obj_req);
498 return 0;
501 while (obj_req->state == WAITING)
502 step_active_slots();
505 * obj_req->req might change when fetching alternates in the callback
506 * process_object_response; therefore, the "shortcut" variable, req,
507 * is used only after we're done with slots.
509 while (obj_req->state == ACTIVE)
510 run_active_slot(obj_req->req->slot);
512 req = obj_req->req;
514 if (req->localfile != -1) {
515 close(req->localfile);
516 req->localfile = -1;
519 normalize_curl_result(&req->curl_result, req->http_code,
520 req->errorstr, sizeof(req->errorstr));
522 if (obj_req->state == ABORTED) {
523 ret = error("Request for %s aborted", hex);
524 } else if (req->curl_result != CURLE_OK &&
525 req->http_code != 416) {
526 if (missing_target(req))
527 ret = -1; /* Be silent, it is probably in a pack. */
528 else
529 ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
530 req->errorstr, req->curl_result,
531 req->http_code, hex);
532 } else if (req->zret != Z_STREAM_END) {
533 walker->corrupt_object_found++;
534 ret = error("File %s (%s) corrupt", hex, req->url);
535 } else if (!oideq(&obj_req->oid, &req->real_oid)) {
536 ret = error("File %s has bad hash", hex);
537 } else if (req->rename < 0) {
538 struct strbuf buf = STRBUF_INIT;
539 loose_object_path(the_repository, &buf, &req->oid);
540 ret = error("unable to write sha1 filename %s", buf.buf);
541 strbuf_release(&buf);
544 release_http_object_request(req);
545 release_object_request(obj_req);
546 return ret;
549 static int fetch(struct walker *walker, unsigned char *hash)
551 struct walker_data *data = walker->data;
552 struct alt_base *altbase = data->alt;
554 if (!fetch_object(walker, hash))
555 return 0;
556 while (altbase) {
557 if (!http_fetch_pack(walker, altbase, hash))
558 return 0;
559 fetch_alternates(walker, data->alt->base);
560 altbase = altbase->next;
562 return error("Unable to find %s under %s", hash_to_hex(hash),
563 data->alt->base);
566 static int fetch_ref(struct walker *walker, struct ref *ref)
568 struct walker_data *data = walker->data;
569 return http_fetch_ref(data->alt->base, ref);
572 static void cleanup(struct walker *walker)
574 struct walker_data *data = walker->data;
575 struct alt_base *alt, *alt_next;
577 if (data) {
578 alt = data->alt;
579 while (alt) {
580 alt_next = alt->next;
582 free(alt->base);
583 free(alt);
585 alt = alt_next;
587 free(data);
588 walker->data = NULL;
592 struct walker *get_http_walker(const char *url)
594 char *s;
595 struct walker_data *data = xmalloc(sizeof(struct walker_data));
596 struct walker *walker = xmalloc(sizeof(struct walker));
598 data->alt = xmalloc(sizeof(*data->alt));
599 data->alt->base = xstrdup(url);
600 for (s = data->alt->base + strlen(data->alt->base) - 1; *s == '/'; --s)
601 *s = 0;
603 data->alt->got_indices = 0;
604 data->alt->packs = NULL;
605 data->alt->next = NULL;
606 data->got_alternates = -1;
608 walker->corrupt_object_found = 0;
609 walker->fetch = fetch;
610 walker->fetch_ref = fetch_ref;
611 walker->prefetch = prefetch;
612 walker->cleanup = cleanup;
613 walker->data = data;
615 add_fill_function(NULL, fill_active_slot);
617 return walker;