Define a built-in attribute macro "binary".
[gitweb.git] / http-fetch.c
index 8fd9de081fc636dc2d5010caea9686caa901c91f..09baedc18ae320a2f09fa61d9065bedb69c02a41 100644 (file)
@@ -7,7 +7,10 @@
 #define PREV_BUF_SIZE 4096
 #define RANGE_HEADER_SIZE 30
 
+static int commits_on_stdin;
+
 static int got_alternates = -1;
+static int corrupt_object_found;
 
 static struct curl_slist *no_pragma_header;
 
@@ -19,7 +22,7 @@ struct alt_base
        struct alt_base *next;
 };
 
-static struct alt_base *alt = NULL;
+static struct alt_base *alt;
 
 enum object_request_state {
        WAITING,
@@ -50,14 +53,14 @@ struct object_request
 };
 
 struct alternates_request {
-       char *base;
+       const char *base;
        char *url;
        struct buffer *buffer;
        struct active_request_slot *slot;
        int http_specific;
 };
 
-static struct object_request *object_queue_head = NULL;
+static struct object_request *object_queue_head;
 
 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
                               void *data)
@@ -67,8 +70,8 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
        int posn = 0;
        struct object_request *obj_req = (struct object_request *)data;
        do {
-               ssize_t retval = write(obj_req->local,
-                                      ptr + posn, size - posn);
+               ssize_t retval = xwrite(obj_req->local,
+                                      (char *) ptr + posn, size - posn);
                if (retval < 0)
                        return posn;
                posn += retval;
@@ -87,7 +90,20 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
        return size;
 }
 
-static void fetch_alternates(char *base);
+static int missing__target(int code, int result)
+{
+       return  /* file:// URL -- do we ever use one??? */
+               (result == CURLE_FILE_COULDNT_READ_FILE) ||
+               /* http:// and https:// URL */
+               (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) ||
+               /* ftp:// URL */
+               (code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE)
+               ;
+}
+
+#define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
+
+static void fetch_alternates(const char *base);
 
 static void process_object_response(void *callback_data);
 
@@ -141,12 +157,12 @@ static void start_object_request(struct object_request *obj_req)
 
        SHA1_Init(&obj_req->c);
 
-       url = xmalloc(strlen(obj_req->repo->base) + 50);
-       obj_req->url = xmalloc(strlen(obj_req->repo->base) + 50);
+       url = xmalloc(strlen(obj_req->repo->base) + 51);
+       obj_req->url = xmalloc(strlen(obj_req->repo->base) + 51);
        strcpy(url, obj_req->repo->base);
        posn = url + strlen(obj_req->repo->base);
-       strcpy(posn, "objects/");
-       posn += 8;
+       strcpy(posn, "/objects/");
+       posn += 9;
        memcpy(posn, hex, 2);
        posn += 2;
        *(posn++) = '/';
@@ -158,7 +174,7 @@ static void start_object_request(struct object_request *obj_req)
        prevlocal = open(prevfile, O_RDONLY);
        if (prevlocal != -1) {
                do {
-                       prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
                        if (prev_read>0) {
                                if (fwrite_sha1_file(prev_buf,
                                                     1,
@@ -182,7 +198,7 @@ static void start_object_request(struct object_request *obj_req)
                SHA1_Init(&obj_req->c);
                if (prev_posn>0) {
                        prev_posn = 0;
-                       lseek(obj_req->local, SEEK_SET, 0);
+                       lseek(obj_req->local, 0, SEEK_SET);
                        ftruncate(obj_req->local, 0);
                }
        }
@@ -244,7 +260,7 @@ static void finish_object_request(struct object_request *obj_req)
                unlink(obj_req->tmpfile);
                return;
        }
-       if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) {
+       if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
                unlink(obj_req->tmpfile);
                return;
        }
@@ -266,8 +282,7 @@ static void process_object_response(void *callback_data)
        obj_req->state = COMPLETE;
 
        /* Use alternates if necessary */
-       if (obj_req->http_code == 404 ||
-           obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE) {
+       if (missing_target(obj_req)) {
                fetch_alternates(alt->base);
                if (obj_req->repo->next != NULL) {
                        obj_req->repo =
@@ -336,7 +351,7 @@ void prefetch(unsigned char *sha1)
        char *filename = sha1_file_name(sha1);
 
        newreq = xmalloc(sizeof(*newreq));
-       memcpy(newreq->sha1, sha1, 20);
+       hashcpy(newreq->sha1, sha1);
        newreq->repo = alt;
        newreq->url = NULL;
        newreq->local = -1;
@@ -344,6 +359,7 @@ void prefetch(unsigned char *sha1)
        snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
        snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
                 "%s.temp", filename);
+       newreq->slot = NULL;
        newreq->next = NULL;
 
        if (object_queue_head == NULL) {
@@ -434,7 +450,7 @@ static int setup_index(struct alt_base *repo, unsigned char *sha1)
 {
        struct packed_git *new_pack;
        if (has_pack_file(sha1))
-               return 0; // don't list this as something we can get
+               return 0; /* don't list this as something we can get */
 
        if (fetch_index(repo, sha1))
                return -1;
@@ -451,7 +467,7 @@ static void process_alternates_response(void *callback_data)
                (struct alternates_request *)callback_data;
        struct active_request_slot *slot = alt_req->slot;
        struct alt_base *tail = alt;
-       char *base = alt_req->base;
+       const char *base = alt_req->base;
        static const char null_byte = '\0';
        char *data;
        int i = 0;
@@ -468,15 +484,18 @@ static void process_alternates_response(void *callback_data)
                                         alt_req->url);
                        active_requests++;
                        slot->in_use = 1;
+                       if (slot->finished != NULL)
+                               (*slot->finished) = 0;
                        if (!start_active_slot(slot)) {
                                got_alternates = -1;
                                slot->in_use = 0;
+                               if (slot->finished != NULL)
+                                       (*slot->finished) = 1;
                        }
                        return;
                }
        } else if (slot->curl_result != CURLE_OK) {
-               if (slot->http_code != 404 &&
-                   slot->curl_result != CURLE_FILE_COULDNT_READ_FILE) {
+               if (!missing_target(slot)) {
                        got_alternates = -1;
                        return;
                }
@@ -496,9 +515,36 @@ static void process_alternates_response(void *callback_data)
                        struct alt_base *newalt;
                        char *target = NULL;
                        if (data[i] == '/') {
-                               serverlen = strchr(base + 8, '/') - base;
-                               okay = 1;
+                               /* This counts
+                                * http://git.host/pub/scm/linux.git/
+                                * -----------here^
+                                * so memcpy(dst, base, serverlen) will
+                                * copy up to "...git.host".
+                                */
+                               const char *colon_ss = strstr(base,"://");
+                               if (colon_ss) {
+                                       serverlen = (strchr(colon_ss + 3, '/')
+                                                    - base);
+                                       okay = 1;
+                               }
                        } else if (!memcmp(data + i, "../", 3)) {
+                               /* Relative URL; chop the corresponding
+                                * number of subpath from base (and ../
+                                * from data), and concatenate the result.
+                                *
+                                * The code first drops ../ from data, and
+                                * then drops one ../ from data and one path
+                                * from base.  IOW, one extra ../ is dropped
+                                * from data than path is dropped from base.
+                                *
+                                * This is not wrong.  The alternate in
+                                *     http://git.host/pub/scm/linux.git/
+                                * to borrow from
+                                *     http://git.host/pub/scm/linus.git/
+                                * is ../../linus.git/objects/.  You need
+                                * two ../../ to borrow from your direct
+                                * neighbour.
+                                */
                                i += 3;
                                serverlen = strlen(base);
                                while (i + 2 < posn &&
@@ -509,7 +555,7 @@ static void process_alternates_response(void *callback_data)
                                                 base[serverlen - 1] != '/');
                                        i += 3;
                                }
-                               // If the server got removed, give up.
+                               /* If the server got removed, give up. */
                                okay = strchr(base, ':') - base + 3 <
                                        serverlen;
                        } else if (alt_req->http_specific) {
@@ -520,13 +566,13 @@ static void process_alternates_response(void *callback_data)
                                        okay = 1;
                                }
                        }
-                       // skip 'objects' at end
+                       /* skip "objects\n" at end */
                        if (okay) {
                                target = xmalloc(serverlen + posn - i - 6);
-                               strncpy(target, base, serverlen);
-                               strncpy(target + serverlen, data + i,
-                                       posn - i - 7);
-                               target[serverlen + posn - i - 7] = '\0';
+                               memcpy(target, base, serverlen);
+                               memcpy(target + serverlen, data + i,
+                                      posn - i - 7);
+                               target[serverlen + posn - i - 7] = 0;
                                if (get_verbosely)
                                        fprintf(stderr,
                                                "Also look at %s\n", target);
@@ -535,6 +581,7 @@ static void process_alternates_response(void *callback_data)
                                newalt->base = target;
                                newalt->got_indices = 0;
                                newalt->packs = NULL;
+
                                while (tail->next != NULL)
                                        tail = tail->next;
                                tail->next = newalt;
@@ -546,7 +593,7 @@ static void process_alternates_response(void *callback_data)
        got_alternates = 1;
 }
 
-static void fetch_alternates(char *base)
+static void fetch_alternates(const char *base)
 {
        struct buffer buffer;
        char *url;
@@ -640,8 +687,7 @@ static int fetch_indices(struct alt_base *repo)
        if (start_active_slot(slot)) {
                run_active_slot(slot);
                if (results.curl_result != CURLE_OK) {
-                       if (results.http_code == 404 ||
-                           results.curl_result == CURLE_FILE_COULDNT_READ_FILE) {
+                       if (missing_target(&results)) {
                                repo->got_indices = 1;
                                free(buffer.buffer);
                                return 0;
@@ -663,8 +709,8 @@ static int fetch_indices(struct alt_base *repo)
                case 'P':
                        i++;
                        if (i + 52 <= buffer.posn &&
-                           !strncmp(data + i, " pack-", 6) &&
-                           !strncmp(data + i + 46, ".pack\n", 6)) {
+                           !prefixcmp(data + i, " pack-") &&
+                           !prefixcmp(data + i + 46, ".pack\n")) {
                                get_sha1_hex(data + i + 6, sha1);
                                setup_index(repo, sha1);
                                i += 51;
@@ -755,6 +801,7 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
                return error("Unable to start request");
        }
 
+       target->pack_size = ftell(packfile);
        fclose(packfile);
 
        ret = move_temp_to_file(tmpfile, filename);
@@ -793,7 +840,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
        int ret = 0;
        struct object_request *obj_req = object_queue_head;
 
-       while (obj_req != NULL && memcmp(obj_req->sha1, sha1, 20))
+       while (obj_req != NULL && hashcmp(obj_req->sha1, sha1))
                obj_req = obj_req->next;
        if (obj_req == NULL)
                return error("Couldn't find request for %s in the queue", hex);
@@ -822,16 +869,16 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
                ret = error("Request for %s aborted", hex);
        } else if (obj_req->curl_result != CURLE_OK &&
                   obj_req->http_code != 416) {
-               if (obj_req->http_code == 404 ||
-                   obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE)
+               if (missing_target(obj_req))
                        ret = -1; /* Be silent, it is probably in a pack. */
                else
                        ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
                                    obj_req->errorstr, obj_req->curl_result,
                                    obj_req->http_code, hex);
        } else if (obj_req->zret != Z_STREAM_END) {
+               corrupt_object_found++;
                ret = error("File %s (%s) corrupt", hex, obj_req->url);
-       } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) {
+       } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
                ret = error("File %s has bad hash", hex);
        } else if (obj_req->rename < 0) {
                ret = error("unable to write sha1 filename %s",
@@ -860,13 +907,14 @@ int fetch(unsigned char *sha1)
 
 static inline int needs_quote(int ch)
 {
-       switch (ch) {
-       case '/': case '-': case '.':
-       case 'A'...'Z': case 'a'...'z': case '0'...'9':
+       if (((ch >= 'A') && (ch <= 'Z'))
+                       || ((ch >= 'a') && (ch <= 'z'))
+                       || ((ch >= '0') && (ch <= '9'))
+                       || (ch == '/')
+                       || (ch == '-')
+                       || (ch == '.'))
                return 0;
-       default:
-               return 1;
-       }
+       return 1;
 }
 
 static inline int hex(int v)
@@ -882,14 +930,14 @@ static char *quote_ref_url(const char *base, const char *ref)
        int len, baselen, ch;
 
        baselen = strlen(base);
-       len = baselen + 6; /* "refs/" + NUL */
+       len = baselen + 7; /* "/refs/" + NUL */
        for (cp = ref; (ch = *cp) != 0; cp++, len++)
                if (needs_quote(ch))
                        len += 2; /* extra two hex plus replacement % */
        qref = xmalloc(len);
        memcpy(qref, base, baselen);
-       memcpy(qref + baselen, "refs/", 5);
-       for (cp = ref, dp = qref + baselen + 5; (ch = *cp) != 0; cp++) {
+       memcpy(qref + baselen, "/refs/", 6);
+       for (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {
                if (needs_quote(ch)) {
                        *dp++ = '%';
                        *dp++ = hex((ch >> 4) & 0xF);
@@ -908,7 +956,7 @@ int fetch_ref(char *ref, unsigned char *sha1)
         char *url;
         char hex[42];
         struct buffer buffer;
-       char *base = alt->base;
+       const char *base = alt->base;
        struct active_request_slot *slot;
        struct slot_results results;
         buffer.size = 41;
@@ -937,14 +985,18 @@ int fetch_ref(char *ref, unsigned char *sha1)
         return 0;
 }
 
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
-       char *commit_id;
-       char *url;
+       int commits;
+       const char **write_ref = NULL;
+       char **commit_id;
+       const char *url;
+       char *s;
        int arg = 1;
        int rc = 0;
 
        setup_git_directory();
+       git_config(git_default_config);
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't') {
@@ -958,36 +1010,55 @@ int main(int argc, char **argv)
                } else if (argv[arg][1] == 'v') {
                        get_verbosely = 1;
                } else if (argv[arg][1] == 'w') {
-                       write_ref = argv[arg + 1];
+                       write_ref = &argv[arg + 1];
                        arg++;
                } else if (!strcmp(argv[arg], "--recover")) {
                        get_recover = 1;
+               } else if (!strcmp(argv[arg], "--stdin")) {
+                       commits_on_stdin = 1;
                }
                arg++;
        }
-       if (argc < arg + 2) {
-               usage("git-http-fetch [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url");
+       if (argc < arg + 2 - commits_on_stdin) {
+               usage("git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url");
                return 1;
        }
-       commit_id = argv[arg];
-       url = argv[arg + 1];
+       if (commits_on_stdin) {
+               commits = pull_targets_stdin(&commit_id, &write_ref);
+       } else {
+               commit_id = (char **) &argv[arg++];
+               commits = 1;
+       }
+       url = argv[arg];
 
        http_init();
 
        no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
 
        alt = xmalloc(sizeof(*alt));
-       alt->base = url;
+       alt->base = xmalloc(strlen(url) + 1);
+       strcpy(alt->base, url);
+       for (s = alt->base + strlen(alt->base) - 1; *s == '/'; --s)
+               *s = 0;
        alt->got_indices = 0;
        alt->packs = NULL;
        alt->next = NULL;
 
-       if (pull(commit_id))
+       if (pull(commits, commit_id, write_ref, url))
                rc = 1;
 
+       http_cleanup();
+
        curl_slist_free_all(no_pragma_header);
 
-       http_cleanup();
+       if (commits_on_stdin)
+               pull_targets_free(commits, commit_id, write_ref);
 
+       if (corrupt_object_found) {
+               fprintf(stderr,
+"Some loose object were found to be corrupt, but they might be just\n"
+"a false '404 Not Found' error message sent with incorrect HTTP\n"
+"status code.  Suggest running git-fsck.\n");
+       }
        return rc;
 }