Code

upload-pack: no longer call rev-list
[git.git] / http-fetch.c
index 61b2188adbb68d94b2d5906be63c89bda879b4b0..396552da022a1dca9c43738be4d87c664f31a06c 100644 (file)
@@ -7,19 +7,23 @@
 #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;
 
 struct alt_base
 {
-       char *base;
+       const char *base;
+       int path_len;
        int got_indices;
        struct packed_git *packs;
        struct alt_base *next;
 };
 
-static struct alt_base *alt = NULL;
+static struct alt_base *alt;
 
 enum object_request_state {
        WAITING,
@@ -50,14 +54,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)
@@ -68,7 +72,7 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
        struct object_request *obj_req = (struct object_request *)data;
        do {
                ssize_t retval = write(obj_req->local,
-                                      ptr + posn, size - posn);
+                                      (char *) ptr + posn, size - posn);
                if (retval < 0)
                        return posn;
                posn += retval;
@@ -87,7 +91,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);
 
@@ -130,7 +147,7 @@ static void start_object_request(struct object_request *obj_req)
 
        if (obj_req->local < 0) {
                obj_req->state = ABORTED;
-               error("Couldn't create temporary file %s for %s: %s\n",
+               error("Couldn't create temporary file %s for %s: %s",
                      obj_req->tmpfile, obj_req->filename, strerror(errno));
                return;
        }
@@ -220,7 +237,6 @@ static void start_object_request(struct object_request *obj_req)
                free(obj_req->url);
                return;
        }
-       
 }
 
 static void finish_object_request(struct object_request *obj_req)
@@ -245,7 +261,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;
        }
@@ -267,8 +283,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 =
@@ -312,7 +327,7 @@ void fill_active_slots(void)
        while (active_requests < max_requests && obj_req != NULL) {
                if (obj_req->state == WAITING) {
                        if (has_sha1_file(obj_req->sha1))
-                               release_object_request(obj_req);
+                               obj_req->state = COMPLETE;
                        else
                                start_object_request(obj_req);
                        curl_multi_perform(curlm, &num_transfers);
@@ -326,7 +341,7 @@ void fill_active_slots(void)
                        slot->curl = NULL;
                }
                slot = slot->next;
-       }                               
+       }
 }
 #endif
 
@@ -337,7 +352,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;
@@ -345,6 +360,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) {
@@ -375,16 +391,17 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
 
        FILE *indexfile;
        struct active_request_slot *slot;
+       struct slot_results results;
 
        if (has_pack_index(sha1))
                return 0;
 
        if (get_verbosely)
                fprintf(stderr, "Getting index for pack %s\n", hex);
-       
+
        url = xmalloc(strlen(repo->base) + 64);
        sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
-       
+
        filename = sha1_pack_index_name(sha1);
        snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
        indexfile = fopen(tmpfile, "a");
@@ -393,6 +410,7 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
                             filename);
 
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
@@ -414,7 +432,7 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
 
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (slot->curl_result != CURLE_OK) {
+               if (results.curl_result != CURLE_OK) {
                        fclose(indexfile);
                        return error("Unable to get pack index %s\n%s", url,
                                     curl_errorstr);
@@ -433,7 +451,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;
@@ -450,7 +468,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;
@@ -467,17 +485,18 @@ static void process_alternates_response(void *callback_data)
                                         alt_req->url);
                        active_requests++;
                        slot->in_use = 1;
-                       if (start_active_slot(slot)) {
-                               return;
-                       } else {
+                       if (slot->finished != NULL)
+                               (*slot->finished) = 0;
+                       if (!start_active_slot(slot)) {
                                got_alternates = -1;
                                slot->in_use = 0;
-                               return;
+                               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,13 +515,41 @@ static void process_alternates_response(void *callback_data)
                        int serverlen = 0;
                        struct alt_base *newalt;
                        char *target = NULL;
+                       char *path;
                        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 && 
+                               while (i + 2 < posn &&
                                       !memcmp(data + i, "../", 3)) {
                                        do {
                                                serverlen--;
@@ -510,8 +557,8 @@ static void process_alternates_response(void *callback_data)
                                                 base[serverlen - 1] != '/');
                                        i += 3;
                                }
-                               // If the server got removed, give up.
-                               okay = strchr(base, ':') - base + 3 < 
+                               /* If the server got removed, give up. */
+                               okay = strchr(base, ':') - base + 3 <
                                        serverlen;
                        } else if (alt_req->http_specific) {
                                char *colon = strchr(data + i, ':');
@@ -521,21 +568,28 @@ 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, 
+                                       fprintf(stderr,
                                                "Also look at %s\n", target);
                                newalt = xmalloc(sizeof(*newalt));
                                newalt->next = NULL;
                                newalt->base = target;
                                newalt->got_indices = 0;
                                newalt->packs = NULL;
+                               path = strstr(target, "//");
+                               if (path) {
+                                       path = strchr(path+2, '/');
+                                       if (path)
+                                               newalt->path_len = strlen(path);
+                               }
+
                                while (tail->next != NULL)
                                        tail = tail->next;
                                tail->next = newalt;
@@ -547,13 +601,13 @@ 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;
        char *data;
        struct active_request_slot *slot;
-       static struct alternates_request alt_req;
+       struct alternates_request alt_req;
 
        /* If another request has already started fetching alternates,
           wait for them to arrive and return to processing this request's
@@ -578,7 +632,7 @@ static void fetch_alternates(char *base)
 
        if (get_verbosely)
                fprintf(stderr, "Getting alternates list for %s\n", base);
-       
+
        url = xmalloc(strlen(base) + 31);
        sprintf(url, "%s/objects/info/http-alternates", base);
 
@@ -616,6 +670,7 @@ static int fetch_indices(struct alt_base *repo)
        int i = 0;
 
        struct active_request_slot *slot;
+       struct slot_results results;
 
        if (repo->got_indices)
                return 0;
@@ -627,20 +682,20 @@ static int fetch_indices(struct alt_base *repo)
 
        if (get_verbosely)
                fprintf(stderr, "Getting pack list for %s\n", repo->base);
-       
+
        url = xmalloc(strlen(repo->base) + 21);
        sprintf(url, "%s/objects/info/packs", repo->base);
 
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (slot->curl_result != CURLE_OK) {
-                       if (slot->http_code == 404 ||
-                           slot->curl_result == CURLE_FILE_COULDNT_READ_FILE) {
+               if (results.curl_result != CURLE_OK) {
+                       if (missing_target(&results)) {
                                repo->got_indices = 1;
                                free(buffer.buffer);
                                return 0;
@@ -695,6 +750,7 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
        struct curl_slist *range_header = NULL;
 
        struct active_request_slot *slot;
+       struct slot_results results;
 
        if (fetch_indices(repo))
                return -1;
@@ -721,6 +777,7 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
                             filename);
 
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
@@ -742,7 +799,7 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
 
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (slot->curl_result != CURLE_OK) {
+               if (results.curl_result != CURLE_OK) {
                        fclose(packfile);
                        return error("Unable to get pack file %s\n%s", url,
                                     curl_errorstr);
@@ -770,19 +827,33 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
        return 0;
 }
 
+static void abort_object_request(struct object_request *obj_req)
+{
+       if (obj_req->local >= 0) {
+               close(obj_req->local);
+               obj_req->local = -1;
+       }
+       unlink(obj_req->tmpfile);
+       if (obj_req->slot) {
+               release_active_slot(obj_req->slot);
+               obj_req->slot = NULL;
+       }
+       release_object_request(obj_req);
+}
+
 static int fetch_object(struct alt_base *repo, unsigned char *sha1)
 {
        char *hex = sha1_to_hex(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);
 
        if (has_sha1_file(obj_req->sha1)) {
-               release_object_request(obj_req);
+               abort_object_request(obj_req);
                return 0;
        }
 
@@ -805,21 +876,20 @@ 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) {
-               ret = error("File %s (%s) corrupt\n", hex, obj_req->url);
-       } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) {
-               ret = error("File %s has bad hash\n", hex);
+               corrupt_object_found++;
+               ret = error("File %s (%s) corrupt", hex, obj_req->url);
+       } 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: %s",
-                           obj_req->filename,
-                           strerror(obj_req->rename));
+               ret = error("unable to write sha1 filename %s",
+                           obj_req->filename);
        }
 
        release_object_request(obj_req);
@@ -838,19 +908,20 @@ int fetch(unsigned char *sha1)
                fetch_alternates(alt->base);
                altbase = altbase->next;
        }
-       return error("Unable to find %s under %s\n", sha1_to_hex(sha1), 
+       return error("Unable to find %s under %s", sha1_to_hex(sha1),
                     alt->base);
 }
 
 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)
@@ -892,22 +963,24 @@ 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;
         buffer.posn = 0;
         buffer.buffer = hex;
         hex[41] = '\0';
-        
+
        url = quote_ref_url(base, ref);
        slot = get_active_slot();
+       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (slot->curl_result != CURLE_OK)
+               if (results.curl_result != CURLE_OK)
                        return error("Couldn't get %s for %s\n%s",
                                     url, ref, curl_errorstr);
        } else {
@@ -919,14 +992,19 @@ 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 *path;
        int arg = 1;
        int rc = 0;
 
+       setup_ident();
        setup_git_directory();
+       git_config(git_default_config);
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't') {
@@ -940,19 +1018,26 @@ 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();
 
@@ -963,13 +1048,28 @@ int main(int argc, char **argv)
        alt->got_indices = 0;
        alt->packs = NULL;
        alt->next = NULL;
+       path = strstr(url, "//");
+       if (path) {
+               path = strchr(path+2, '/');
+               if (path)
+                       alt->path_len = strlen(path);
+       }
 
-       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-objects.\n");
+       }
        return rc;
 }