Add 'filter' attribute and external filter driver definition.
[git/jnareb-git/bp-gitweb.git] / http-fetch.c
blob09baedc18ae320a2f09fa61d9065bedb69c02a41
1 #include "cache.h"
2 #include "commit.h"
3 #include "pack.h"
4 #include "fetch.h"
5 #include "http.h"
7 #define PREV_BUF_SIZE 4096
8 #define RANGE_HEADER_SIZE 30
10 static int commits_on_stdin;
12 static int got_alternates = -1;
13 static int corrupt_object_found;
15 static struct curl_slist *no_pragma_header;
17 struct alt_base
19 char *base;
20 int got_indices;
21 struct packed_git *packs;
22 struct alt_base *next;
25 static struct alt_base *alt;
27 enum object_request_state {
28 WAITING,
29 ABORTED,
30 ACTIVE,
31 COMPLETE,
34 struct object_request
36 unsigned char sha1[20];
37 struct alt_base *repo;
38 char *url;
39 char filename[PATH_MAX];
40 char tmpfile[PATH_MAX];
41 int local;
42 enum object_request_state state;
43 CURLcode curl_result;
44 char errorstr[CURL_ERROR_SIZE];
45 long http_code;
46 unsigned char real_sha1[20];
47 SHA_CTX c;
48 z_stream stream;
49 int zret;
50 int rename;
51 struct active_request_slot *slot;
52 struct object_request *next;
55 struct alternates_request {
56 const char *base;
57 char *url;
58 struct buffer *buffer;
59 struct active_request_slot *slot;
60 int http_specific;
63 static struct object_request *object_queue_head;
65 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
66 void *data)
68 unsigned char expn[4096];
69 size_t size = eltsize * nmemb;
70 int posn = 0;
71 struct object_request *obj_req = (struct object_request *)data;
72 do {
73 ssize_t retval = xwrite(obj_req->local,
74 (char *) ptr + posn, size - posn);
75 if (retval < 0)
76 return posn;
77 posn += retval;
78 } while (posn < size);
80 obj_req->stream.avail_in = size;
81 obj_req->stream.next_in = ptr;
82 do {
83 obj_req->stream.next_out = expn;
84 obj_req->stream.avail_out = sizeof(expn);
85 obj_req->zret = inflate(&obj_req->stream, Z_SYNC_FLUSH);
86 SHA1_Update(&obj_req->c, expn,
87 sizeof(expn) - obj_req->stream.avail_out);
88 } while (obj_req->stream.avail_in && obj_req->zret == Z_OK);
89 data_received++;
90 return size;
93 static int missing__target(int code, int result)
95 return /* file:// URL -- do we ever use one??? */
96 (result == CURLE_FILE_COULDNT_READ_FILE) ||
97 /* http:// and https:// URL */
98 (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) ||
99 /* ftp:// URL */
100 (code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE)
104 #define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
106 static void fetch_alternates(const char *base);
108 static void process_object_response(void *callback_data);
110 static void start_object_request(struct object_request *obj_req)
112 char *hex = sha1_to_hex(obj_req->sha1);
113 char prevfile[PATH_MAX];
114 char *url;
115 char *posn;
116 int prevlocal;
117 unsigned char prev_buf[PREV_BUF_SIZE];
118 ssize_t prev_read = 0;
119 long prev_posn = 0;
120 char range[RANGE_HEADER_SIZE];
121 struct curl_slist *range_header = NULL;
122 struct active_request_slot *slot;
124 snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename);
125 unlink(prevfile);
126 rename(obj_req->tmpfile, prevfile);
127 unlink(obj_req->tmpfile);
129 if (obj_req->local != -1)
130 error("fd leakage in start: %d", obj_req->local);
131 obj_req->local = open(obj_req->tmpfile,
132 O_WRONLY | O_CREAT | O_EXCL, 0666);
133 /* This could have failed due to the "lazy directory creation";
134 * try to mkdir the last path component.
136 if (obj_req->local < 0 && errno == ENOENT) {
137 char *dir = strrchr(obj_req->tmpfile, '/');
138 if (dir) {
139 *dir = 0;
140 mkdir(obj_req->tmpfile, 0777);
141 *dir = '/';
143 obj_req->local = open(obj_req->tmpfile,
144 O_WRONLY | O_CREAT | O_EXCL, 0666);
147 if (obj_req->local < 0) {
148 obj_req->state = ABORTED;
149 error("Couldn't create temporary file %s for %s: %s",
150 obj_req->tmpfile, obj_req->filename, strerror(errno));
151 return;
154 memset(&obj_req->stream, 0, sizeof(obj_req->stream));
156 inflateInit(&obj_req->stream);
158 SHA1_Init(&obj_req->c);
160 url = xmalloc(strlen(obj_req->repo->base) + 51);
161 obj_req->url = xmalloc(strlen(obj_req->repo->base) + 51);
162 strcpy(url, obj_req->repo->base);
163 posn = url + strlen(obj_req->repo->base);
164 strcpy(posn, "/objects/");
165 posn += 9;
166 memcpy(posn, hex, 2);
167 posn += 2;
168 *(posn++) = '/';
169 strcpy(posn, hex + 2);
170 strcpy(obj_req->url, url);
172 /* If a previous temp file is present, process what was already
173 fetched. */
174 prevlocal = open(prevfile, O_RDONLY);
175 if (prevlocal != -1) {
176 do {
177 prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
178 if (prev_read>0) {
179 if (fwrite_sha1_file(prev_buf,
181 prev_read,
182 obj_req) == prev_read) {
183 prev_posn += prev_read;
184 } else {
185 prev_read = -1;
188 } while (prev_read > 0);
189 close(prevlocal);
191 unlink(prevfile);
193 /* Reset inflate/SHA1 if there was an error reading the previous temp
194 file; also rewind to the beginning of the local file. */
195 if (prev_read == -1) {
196 memset(&obj_req->stream, 0, sizeof(obj_req->stream));
197 inflateInit(&obj_req->stream);
198 SHA1_Init(&obj_req->c);
199 if (prev_posn>0) {
200 prev_posn = 0;
201 lseek(obj_req->local, 0, SEEK_SET);
202 ftruncate(obj_req->local, 0);
206 slot = get_active_slot();
207 slot->callback_func = process_object_response;
208 slot->callback_data = obj_req;
209 obj_req->slot = slot;
211 curl_easy_setopt(slot->curl, CURLOPT_FILE, obj_req);
212 curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
213 curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr);
214 curl_easy_setopt(slot->curl, CURLOPT_URL, url);
215 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
217 /* If we have successfully processed data from a previous fetch
218 attempt, only fetch the data we don't already have. */
219 if (prev_posn>0) {
220 if (get_verbosely)
221 fprintf(stderr,
222 "Resuming fetch of object %s at byte %ld\n",
223 hex, prev_posn);
224 sprintf(range, "Range: bytes=%ld-", prev_posn);
225 range_header = curl_slist_append(range_header, range);
226 curl_easy_setopt(slot->curl,
227 CURLOPT_HTTPHEADER, range_header);
230 /* Try to get the request started, abort the request on error */
231 obj_req->state = ACTIVE;
232 if (!start_active_slot(slot)) {
233 obj_req->state = ABORTED;
234 obj_req->slot = NULL;
235 close(obj_req->local); obj_req->local = -1;
236 free(obj_req->url);
237 return;
241 static void finish_object_request(struct object_request *obj_req)
243 struct stat st;
245 fchmod(obj_req->local, 0444);
246 close(obj_req->local); obj_req->local = -1;
248 if (obj_req->http_code == 416) {
249 fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
250 } else if (obj_req->curl_result != CURLE_OK) {
251 if (stat(obj_req->tmpfile, &st) == 0)
252 if (st.st_size == 0)
253 unlink(obj_req->tmpfile);
254 return;
257 inflateEnd(&obj_req->stream);
258 SHA1_Final(obj_req->real_sha1, &obj_req->c);
259 if (obj_req->zret != Z_STREAM_END) {
260 unlink(obj_req->tmpfile);
261 return;
263 if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
264 unlink(obj_req->tmpfile);
265 return;
267 obj_req->rename =
268 move_temp_to_file(obj_req->tmpfile, obj_req->filename);
270 if (obj_req->rename == 0)
271 pull_say("got %s\n", sha1_to_hex(obj_req->sha1));
274 static void process_object_response(void *callback_data)
276 struct object_request *obj_req =
277 (struct object_request *)callback_data;
279 obj_req->curl_result = obj_req->slot->curl_result;
280 obj_req->http_code = obj_req->slot->http_code;
281 obj_req->slot = NULL;
282 obj_req->state = COMPLETE;
284 /* Use alternates if necessary */
285 if (missing_target(obj_req)) {
286 fetch_alternates(alt->base);
287 if (obj_req->repo->next != NULL) {
288 obj_req->repo =
289 obj_req->repo->next;
290 close(obj_req->local);
291 obj_req->local = -1;
292 start_object_request(obj_req);
293 return;
297 finish_object_request(obj_req);
300 static void release_object_request(struct object_request *obj_req)
302 struct object_request *entry = object_queue_head;
304 if (obj_req->local != -1)
305 error("fd leakage in release: %d", obj_req->local);
306 if (obj_req == object_queue_head) {
307 object_queue_head = obj_req->next;
308 } else {
309 while (entry->next != NULL && entry->next != obj_req)
310 entry = entry->next;
311 if (entry->next == obj_req)
312 entry->next = entry->next->next;
315 free(obj_req->url);
316 free(obj_req);
319 #ifdef USE_CURL_MULTI
320 void fill_active_slots(void)
322 struct object_request *obj_req = object_queue_head;
323 struct active_request_slot *slot = active_queue_head;
324 int num_transfers;
326 while (active_requests < max_requests && obj_req != NULL) {
327 if (obj_req->state == WAITING) {
328 if (has_sha1_file(obj_req->sha1))
329 obj_req->state = COMPLETE;
330 else
331 start_object_request(obj_req);
332 curl_multi_perform(curlm, &num_transfers);
334 obj_req = obj_req->next;
337 while (slot != NULL) {
338 if (!slot->in_use && slot->curl != NULL) {
339 curl_easy_cleanup(slot->curl);
340 slot->curl = NULL;
342 slot = slot->next;
345 #endif
347 void prefetch(unsigned char *sha1)
349 struct object_request *newreq;
350 struct object_request *tail;
351 char *filename = sha1_file_name(sha1);
353 newreq = xmalloc(sizeof(*newreq));
354 hashcpy(newreq->sha1, sha1);
355 newreq->repo = alt;
356 newreq->url = NULL;
357 newreq->local = -1;
358 newreq->state = WAITING;
359 snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
360 snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
361 "%s.temp", filename);
362 newreq->slot = NULL;
363 newreq->next = NULL;
365 if (object_queue_head == NULL) {
366 object_queue_head = newreq;
367 } else {
368 tail = object_queue_head;
369 while (tail->next != NULL) {
370 tail = tail->next;
372 tail->next = newreq;
375 #ifdef USE_CURL_MULTI
376 fill_active_slots();
377 step_active_slots();
378 #endif
381 static int fetch_index(struct alt_base *repo, unsigned char *sha1)
383 char *hex = sha1_to_hex(sha1);
384 char *filename;
385 char *url;
386 char tmpfile[PATH_MAX];
387 long prev_posn = 0;
388 char range[RANGE_HEADER_SIZE];
389 struct curl_slist *range_header = NULL;
391 FILE *indexfile;
392 struct active_request_slot *slot;
393 struct slot_results results;
395 if (has_pack_index(sha1))
396 return 0;
398 if (get_verbosely)
399 fprintf(stderr, "Getting index for pack %s\n", hex);
401 url = xmalloc(strlen(repo->base) + 64);
402 sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
404 filename = sha1_pack_index_name(sha1);
405 snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
406 indexfile = fopen(tmpfile, "a");
407 if (!indexfile)
408 return error("Unable to open local file %s for pack index",
409 filename);
411 slot = get_active_slot();
412 slot->results = &results;
413 curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
414 curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
415 curl_easy_setopt(slot->curl, CURLOPT_URL, url);
416 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
417 slot->local = indexfile;
419 /* If there is data present from a previous transfer attempt,
420 resume where it left off */
421 prev_posn = ftell(indexfile);
422 if (prev_posn>0) {
423 if (get_verbosely)
424 fprintf(stderr,
425 "Resuming fetch of index for pack %s at byte %ld\n",
426 hex, prev_posn);
427 sprintf(range, "Range: bytes=%ld-", prev_posn);
428 range_header = curl_slist_append(range_header, range);
429 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
432 if (start_active_slot(slot)) {
433 run_active_slot(slot);
434 if (results.curl_result != CURLE_OK) {
435 fclose(indexfile);
436 return error("Unable to get pack index %s\n%s", url,
437 curl_errorstr);
439 } else {
440 fclose(indexfile);
441 return error("Unable to start request");
444 fclose(indexfile);
446 return move_temp_to_file(tmpfile, filename);
449 static int setup_index(struct alt_base *repo, unsigned char *sha1)
451 struct packed_git *new_pack;
452 if (has_pack_file(sha1))
453 return 0; /* don't list this as something we can get */
455 if (fetch_index(repo, sha1))
456 return -1;
458 new_pack = parse_pack_index(sha1);
459 new_pack->next = repo->packs;
460 repo->packs = new_pack;
461 return 0;
464 static void process_alternates_response(void *callback_data)
466 struct alternates_request *alt_req =
467 (struct alternates_request *)callback_data;
468 struct active_request_slot *slot = alt_req->slot;
469 struct alt_base *tail = alt;
470 const char *base = alt_req->base;
471 static const char null_byte = '\0';
472 char *data;
473 int i = 0;
475 if (alt_req->http_specific) {
476 if (slot->curl_result != CURLE_OK ||
477 !alt_req->buffer->posn) {
479 /* Try reusing the slot to get non-http alternates */
480 alt_req->http_specific = 0;
481 sprintf(alt_req->url, "%s/objects/info/alternates",
482 base);
483 curl_easy_setopt(slot->curl, CURLOPT_URL,
484 alt_req->url);
485 active_requests++;
486 slot->in_use = 1;
487 if (slot->finished != NULL)
488 (*slot->finished) = 0;
489 if (!start_active_slot(slot)) {
490 got_alternates = -1;
491 slot->in_use = 0;
492 if (slot->finished != NULL)
493 (*slot->finished) = 1;
495 return;
497 } else if (slot->curl_result != CURLE_OK) {
498 if (!missing_target(slot)) {
499 got_alternates = -1;
500 return;
504 fwrite_buffer(&null_byte, 1, 1, alt_req->buffer);
505 alt_req->buffer->posn--;
506 data = alt_req->buffer->buffer;
508 while (i < alt_req->buffer->posn) {
509 int posn = i;
510 while (posn < alt_req->buffer->posn && data[posn] != '\n')
511 posn++;
512 if (data[posn] == '\n') {
513 int okay = 0;
514 int serverlen = 0;
515 struct alt_base *newalt;
516 char *target = NULL;
517 if (data[i] == '/') {
518 /* This counts
519 * http://git.host/pub/scm/linux.git/
520 * -----------here^
521 * so memcpy(dst, base, serverlen) will
522 * copy up to "...git.host".
524 const char *colon_ss = strstr(base,"://");
525 if (colon_ss) {
526 serverlen = (strchr(colon_ss + 3, '/')
527 - base);
528 okay = 1;
530 } else if (!memcmp(data + i, "../", 3)) {
531 /* Relative URL; chop the corresponding
532 * number of subpath from base (and ../
533 * from data), and concatenate the result.
535 * The code first drops ../ from data, and
536 * then drops one ../ from data and one path
537 * from base. IOW, one extra ../ is dropped
538 * from data than path is dropped from base.
540 * This is not wrong. The alternate in
541 * http://git.host/pub/scm/linux.git/
542 * to borrow from
543 * http://git.host/pub/scm/linus.git/
544 * is ../../linus.git/objects/. You need
545 * two ../../ to borrow from your direct
546 * neighbour.
548 i += 3;
549 serverlen = strlen(base);
550 while (i + 2 < posn &&
551 !memcmp(data + i, "../", 3)) {
552 do {
553 serverlen--;
554 } while (serverlen &&
555 base[serverlen - 1] != '/');
556 i += 3;
558 /* If the server got removed, give up. */
559 okay = strchr(base, ':') - base + 3 <
560 serverlen;
561 } else if (alt_req->http_specific) {
562 char *colon = strchr(data + i, ':');
563 char *slash = strchr(data + i, '/');
564 if (colon && slash && colon < data + posn &&
565 slash < data + posn && colon < slash) {
566 okay = 1;
569 /* skip "objects\n" at end */
570 if (okay) {
571 target = xmalloc(serverlen + posn - i - 6);
572 memcpy(target, base, serverlen);
573 memcpy(target + serverlen, data + i,
574 posn - i - 7);
575 target[serverlen + posn - i - 7] = 0;
576 if (get_verbosely)
577 fprintf(stderr,
578 "Also look at %s\n", target);
579 newalt = xmalloc(sizeof(*newalt));
580 newalt->next = NULL;
581 newalt->base = target;
582 newalt->got_indices = 0;
583 newalt->packs = NULL;
585 while (tail->next != NULL)
586 tail = tail->next;
587 tail->next = newalt;
590 i = posn + 1;
593 got_alternates = 1;
596 static void fetch_alternates(const char *base)
598 struct buffer buffer;
599 char *url;
600 char *data;
601 struct active_request_slot *slot;
602 struct alternates_request alt_req;
604 /* If another request has already started fetching alternates,
605 wait for them to arrive and return to processing this request's
606 curl message */
607 #ifdef USE_CURL_MULTI
608 while (got_alternates == 0) {
609 step_active_slots();
611 #endif
613 /* Nothing to do if they've already been fetched */
614 if (got_alternates == 1)
615 return;
617 /* Start the fetch */
618 got_alternates = 0;
620 data = xmalloc(4096);
621 buffer.size = 4096;
622 buffer.posn = 0;
623 buffer.buffer = data;
625 if (get_verbosely)
626 fprintf(stderr, "Getting alternates list for %s\n", base);
628 url = xmalloc(strlen(base) + 31);
629 sprintf(url, "%s/objects/info/http-alternates", base);
631 /* Use a callback to process the result, since another request
632 may fail and need to have alternates loaded before continuing */
633 slot = get_active_slot();
634 slot->callback_func = process_alternates_response;
635 slot->callback_data = &alt_req;
637 curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
638 curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
639 curl_easy_setopt(slot->curl, CURLOPT_URL, url);
641 alt_req.base = base;
642 alt_req.url = url;
643 alt_req.buffer = &buffer;
644 alt_req.http_specific = 1;
645 alt_req.slot = slot;
647 if (start_active_slot(slot))
648 run_active_slot(slot);
649 else
650 got_alternates = -1;
652 free(data);
653 free(url);
656 static int fetch_indices(struct alt_base *repo)
658 unsigned char sha1[20];
659 char *url;
660 struct buffer buffer;
661 char *data;
662 int i = 0;
664 struct active_request_slot *slot;
665 struct slot_results results;
667 if (repo->got_indices)
668 return 0;
670 data = xmalloc(4096);
671 buffer.size = 4096;
672 buffer.posn = 0;
673 buffer.buffer = data;
675 if (get_verbosely)
676 fprintf(stderr, "Getting pack list for %s\n", repo->base);
678 url = xmalloc(strlen(repo->base) + 21);
679 sprintf(url, "%s/objects/info/packs", repo->base);
681 slot = get_active_slot();
682 slot->results = &results;
683 curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
684 curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
685 curl_easy_setopt(slot->curl, CURLOPT_URL, url);
686 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
687 if (start_active_slot(slot)) {
688 run_active_slot(slot);
689 if (results.curl_result != CURLE_OK) {
690 if (missing_target(&results)) {
691 repo->got_indices = 1;
692 free(buffer.buffer);
693 return 0;
694 } else {
695 repo->got_indices = 0;
696 free(buffer.buffer);
697 return error("%s", curl_errorstr);
700 } else {
701 repo->got_indices = 0;
702 free(buffer.buffer);
703 return error("Unable to start request");
706 data = buffer.buffer;
707 while (i < buffer.posn) {
708 switch (data[i]) {
709 case 'P':
710 i++;
711 if (i + 52 <= buffer.posn &&
712 !prefixcmp(data + i, " pack-") &&
713 !prefixcmp(data + i + 46, ".pack\n")) {
714 get_sha1_hex(data + i + 6, sha1);
715 setup_index(repo, sha1);
716 i += 51;
717 break;
719 default:
720 while (i < buffer.posn && data[i] != '\n')
721 i++;
723 i++;
726 free(buffer.buffer);
727 repo->got_indices = 1;
728 return 0;
731 static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
733 char *url;
734 struct packed_git *target;
735 struct packed_git **lst;
736 FILE *packfile;
737 char *filename;
738 char tmpfile[PATH_MAX];
739 int ret;
740 long prev_posn = 0;
741 char range[RANGE_HEADER_SIZE];
742 struct curl_slist *range_header = NULL;
744 struct active_request_slot *slot;
745 struct slot_results results;
747 if (fetch_indices(repo))
748 return -1;
749 target = find_sha1_pack(sha1, repo->packs);
750 if (!target)
751 return -1;
753 if (get_verbosely) {
754 fprintf(stderr, "Getting pack %s\n",
755 sha1_to_hex(target->sha1));
756 fprintf(stderr, " which contains %s\n",
757 sha1_to_hex(sha1));
760 url = xmalloc(strlen(repo->base) + 65);
761 sprintf(url, "%s/objects/pack/pack-%s.pack",
762 repo->base, sha1_to_hex(target->sha1));
764 filename = sha1_pack_name(target->sha1);
765 snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
766 packfile = fopen(tmpfile, "a");
767 if (!packfile)
768 return error("Unable to open local file %s for pack",
769 filename);
771 slot = get_active_slot();
772 slot->results = &results;
773 curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
774 curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
775 curl_easy_setopt(slot->curl, CURLOPT_URL, url);
776 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
777 slot->local = packfile;
779 /* If there is data present from a previous transfer attempt,
780 resume where it left off */
781 prev_posn = ftell(packfile);
782 if (prev_posn>0) {
783 if (get_verbosely)
784 fprintf(stderr,
785 "Resuming fetch of pack %s at byte %ld\n",
786 sha1_to_hex(target->sha1), prev_posn);
787 sprintf(range, "Range: bytes=%ld-", prev_posn);
788 range_header = curl_slist_append(range_header, range);
789 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
792 if (start_active_slot(slot)) {
793 run_active_slot(slot);
794 if (results.curl_result != CURLE_OK) {
795 fclose(packfile);
796 return error("Unable to get pack file %s\n%s", url,
797 curl_errorstr);
799 } else {
800 fclose(packfile);
801 return error("Unable to start request");
804 target->pack_size = ftell(packfile);
805 fclose(packfile);
807 ret = move_temp_to_file(tmpfile, filename);
808 if (ret)
809 return ret;
811 lst = &repo->packs;
812 while (*lst != target)
813 lst = &((*lst)->next);
814 *lst = (*lst)->next;
816 if (verify_pack(target, 0))
817 return -1;
818 install_packed_git(target);
820 return 0;
823 static void abort_object_request(struct object_request *obj_req)
825 if (obj_req->local >= 0) {
826 close(obj_req->local);
827 obj_req->local = -1;
829 unlink(obj_req->tmpfile);
830 if (obj_req->slot) {
831 release_active_slot(obj_req->slot);
832 obj_req->slot = NULL;
834 release_object_request(obj_req);
837 static int fetch_object(struct alt_base *repo, unsigned char *sha1)
839 char *hex = sha1_to_hex(sha1);
840 int ret = 0;
841 struct object_request *obj_req = object_queue_head;
843 while (obj_req != NULL && hashcmp(obj_req->sha1, sha1))
844 obj_req = obj_req->next;
845 if (obj_req == NULL)
846 return error("Couldn't find request for %s in the queue", hex);
848 if (has_sha1_file(obj_req->sha1)) {
849 abort_object_request(obj_req);
850 return 0;
853 #ifdef USE_CURL_MULTI
854 while (obj_req->state == WAITING) {
855 step_active_slots();
857 #else
858 start_object_request(obj_req);
859 #endif
861 while (obj_req->state == ACTIVE) {
862 run_active_slot(obj_req->slot);
864 if (obj_req->local != -1) {
865 close(obj_req->local); obj_req->local = -1;
868 if (obj_req->state == ABORTED) {
869 ret = error("Request for %s aborted", hex);
870 } else if (obj_req->curl_result != CURLE_OK &&
871 obj_req->http_code != 416) {
872 if (missing_target(obj_req))
873 ret = -1; /* Be silent, it is probably in a pack. */
874 else
875 ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
876 obj_req->errorstr, obj_req->curl_result,
877 obj_req->http_code, hex);
878 } else if (obj_req->zret != Z_STREAM_END) {
879 corrupt_object_found++;
880 ret = error("File %s (%s) corrupt", hex, obj_req->url);
881 } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
882 ret = error("File %s has bad hash", hex);
883 } else if (obj_req->rename < 0) {
884 ret = error("unable to write sha1 filename %s",
885 obj_req->filename);
888 release_object_request(obj_req);
889 return ret;
892 int fetch(unsigned char *sha1)
894 struct alt_base *altbase = alt;
896 if (!fetch_object(altbase, sha1))
897 return 0;
898 while (altbase) {
899 if (!fetch_pack(altbase, sha1))
900 return 0;
901 fetch_alternates(alt->base);
902 altbase = altbase->next;
904 return error("Unable to find %s under %s", sha1_to_hex(sha1),
905 alt->base);
908 static inline int needs_quote(int ch)
910 if (((ch >= 'A') && (ch <= 'Z'))
911 || ((ch >= 'a') && (ch <= 'z'))
912 || ((ch >= '0') && (ch <= '9'))
913 || (ch == '/')
914 || (ch == '-')
915 || (ch == '.'))
916 return 0;
917 return 1;
920 static inline int hex(int v)
922 if (v < 10) return '0' + v;
923 else return 'A' + v - 10;
926 static char *quote_ref_url(const char *base, const char *ref)
928 const char *cp;
929 char *dp, *qref;
930 int len, baselen, ch;
932 baselen = strlen(base);
933 len = baselen + 7; /* "/refs/" + NUL */
934 for (cp = ref; (ch = *cp) != 0; cp++, len++)
935 if (needs_quote(ch))
936 len += 2; /* extra two hex plus replacement % */
937 qref = xmalloc(len);
938 memcpy(qref, base, baselen);
939 memcpy(qref + baselen, "/refs/", 6);
940 for (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {
941 if (needs_quote(ch)) {
942 *dp++ = '%';
943 *dp++ = hex((ch >> 4) & 0xF);
944 *dp++ = hex(ch & 0xF);
946 else
947 *dp++ = ch;
949 *dp = 0;
951 return qref;
954 int fetch_ref(char *ref, unsigned char *sha1)
956 char *url;
957 char hex[42];
958 struct buffer buffer;
959 const char *base = alt->base;
960 struct active_request_slot *slot;
961 struct slot_results results;
962 buffer.size = 41;
963 buffer.posn = 0;
964 buffer.buffer = hex;
965 hex[41] = '\0';
967 url = quote_ref_url(base, ref);
968 slot = get_active_slot();
969 slot->results = &results;
970 curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
971 curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
972 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
973 curl_easy_setopt(slot->curl, CURLOPT_URL, url);
974 if (start_active_slot(slot)) {
975 run_active_slot(slot);
976 if (results.curl_result != CURLE_OK)
977 return error("Couldn't get %s for %s\n%s",
978 url, ref, curl_errorstr);
979 } else {
980 return error("Unable to start request");
983 hex[40] = '\0';
984 get_sha1_hex(hex, sha1);
985 return 0;
988 int main(int argc, const char **argv)
990 int commits;
991 const char **write_ref = NULL;
992 char **commit_id;
993 const char *url;
994 char *s;
995 int arg = 1;
996 int rc = 0;
998 setup_git_directory();
999 git_config(git_default_config);
1001 while (arg < argc && argv[arg][0] == '-') {
1002 if (argv[arg][1] == 't') {
1003 get_tree = 1;
1004 } else if (argv[arg][1] == 'c') {
1005 get_history = 1;
1006 } else if (argv[arg][1] == 'a') {
1007 get_all = 1;
1008 get_tree = 1;
1009 get_history = 1;
1010 } else if (argv[arg][1] == 'v') {
1011 get_verbosely = 1;
1012 } else if (argv[arg][1] == 'w') {
1013 write_ref = &argv[arg + 1];
1014 arg++;
1015 } else if (!strcmp(argv[arg], "--recover")) {
1016 get_recover = 1;
1017 } else if (!strcmp(argv[arg], "--stdin")) {
1018 commits_on_stdin = 1;
1020 arg++;
1022 if (argc < arg + 2 - commits_on_stdin) {
1023 usage("git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url");
1024 return 1;
1026 if (commits_on_stdin) {
1027 commits = pull_targets_stdin(&commit_id, &write_ref);
1028 } else {
1029 commit_id = (char **) &argv[arg++];
1030 commits = 1;
1032 url = argv[arg];
1034 http_init();
1036 no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
1038 alt = xmalloc(sizeof(*alt));
1039 alt->base = xmalloc(strlen(url) + 1);
1040 strcpy(alt->base, url);
1041 for (s = alt->base + strlen(alt->base) - 1; *s == '/'; --s)
1042 *s = 0;
1043 alt->got_indices = 0;
1044 alt->packs = NULL;
1045 alt->next = NULL;
1047 if (pull(commits, commit_id, write_ref, url))
1048 rc = 1;
1050 http_cleanup();
1052 curl_slist_free_all(no_pragma_header);
1054 if (commits_on_stdin)
1055 pull_targets_free(commits, commit_id, write_ref);
1057 if (corrupt_object_found) {
1058 fprintf(stderr,
1059 "Some loose object were found to be corrupt, but they might be just\n"
1060 "a false '404 Not Found' error message sent with incorrect HTTP\n"
1061 "status code. Suggest running git-fsck.\n");
1063 return rc;