pack-bitmap: implement optional name_hash cache
[gitweb.git] / remote-curl.c
index 933c69ac2627c1903276b92b457843e12649f39e..b5ebe0180069c1c1f96b769a37c5f3eb9459dfe9 100644 (file)
@@ -6,7 +6,9 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "pkt-line.h"
+#include "string-list.h"
 #include "sideband.h"
+#include "argv-array.h"
 
 static struct remote *remote;
 static const char *url; /* always ends with a trailing slash */
@@ -15,11 +17,13 @@ struct options {
        int verbosity;
        unsigned long depth;
        unsigned progress : 1,
+               check_self_contained_and_connected : 1,
                followtags : 1,
                dry_run : 1,
                thin : 1;
 };
 static struct options options;
+static struct string_list cas_options = STRING_LIST_INIT_DUP;
 
 static int set_option(const char *name, const char *value)
 {
@@ -66,6 +70,22 @@ static int set_option(const char *name, const char *value)
                        return -1;
                return 0;
        }
+       else if (!strcmp(name, "check-connectivity")) {
+               if (!strcmp(value, "true"))
+                       options.check_self_contained_and_connected = 1;
+               else if (!strcmp(value, "false"))
+                       options.check_self_contained_and_connected = 0;
+               else
+                       return -1;
+               return 0;
+       }
+       else if (!strcmp(name, "cas")) {
+               struct strbuf val = STRBUF_INIT;
+               strbuf_addf(&val, "--" CAS_OPT_NAME "=%s", value);
+               string_list_append(&cas_options, val.buf);
+               strbuf_release(&val);
+               return 0;
+       }
        else {
                return 1 /* unsupported */;
        }
@@ -76,21 +96,109 @@ struct discovery {
        char *buf_alloc;
        char *buf;
        size_t len;
+       struct ref *refs;
        unsigned proto_git : 1;
 };
 static struct discovery *last_discovery;
 
+static struct ref *parse_git_refs(struct discovery *heads, int for_push)
+{
+       struct ref *list = NULL;
+       get_remote_heads(-1, heads->buf, heads->len, &list,
+                        for_push ? REF_NORMAL : 0, NULL);
+       return list;
+}
+
+static struct ref *parse_info_refs(struct discovery *heads)
+{
+       char *data, *start, *mid;
+       char *ref_name;
+       int i = 0;
+
+       struct ref *refs = NULL;
+       struct ref *ref = NULL;
+       struct ref *last_ref = NULL;
+
+       data = heads->buf;
+       start = NULL;
+       mid = data;
+       while (i < heads->len) {
+               if (!start) {
+                       start = &data[i];
+               }
+               if (data[i] == '\t')
+                       mid = &data[i];
+               if (data[i] == '\n') {
+                       if (mid - start != 40)
+                               die("%sinfo/refs not valid: is this a git repository?", url);
+                       data[i] = 0;
+                       ref_name = mid + 1;
+                       ref = xmalloc(sizeof(struct ref) +
+                                     strlen(ref_name) + 1);
+                       memset(ref, 0, sizeof(struct ref));
+                       strcpy(ref->name, ref_name);
+                       get_sha1_hex(start, ref->old_sha1);
+                       if (!refs)
+                               refs = ref;
+                       if (last_ref)
+                               last_ref->next = ref;
+                       last_ref = ref;
+                       start = NULL;
+               }
+               i++;
+       }
+
+       ref = alloc_ref("HEAD");
+       if (!http_fetch_ref(url, ref) &&
+           !resolve_remote_symref(ref, refs)) {
+               ref->next = refs;
+               refs = ref;
+       } else {
+               free(ref);
+       }
+
+       return refs;
+}
+
 static void free_discovery(struct discovery *d)
 {
        if (d) {
                if (d == last_discovery)
                        last_discovery = NULL;
                free(d->buf_alloc);
+               free_refs(d->refs);
                free(d);
        }
 }
 
-static struct discovery* discover_refs(const char *service)
+static int show_http_message(struct strbuf *type, struct strbuf *msg)
+{
+       const char *p, *eol;
+
+       /*
+        * We only show text/plain parts, as other types are likely
+        * to be ugly to look at on the user's terminal.
+        *
+        * TODO should handle "; charset=XXX", and re-encode into
+        * logoutputencoding
+        */
+       if (strcasecmp(type->buf, "text/plain"))
+               return -1;
+
+       strbuf_trim(msg);
+       if (!msg->len)
+               return -1;
+
+       p = msg->buf;
+       do {
+               eol = strchrnul(p, '\n');
+               fprintf(stderr, "remote: %.*s\n", (int)(eol - p), p);
+               p = eol + 1;
+       } while(*eol);
+       return 0;
+}
+
+static struct discovery* discover_refs(const char *service, int for_push)
 {
        struct strbuf exp = STRBUF_INIT;
        struct strbuf type = STRBUF_INIT;
@@ -115,18 +223,20 @@ static struct discovery* discover_refs(const char *service)
        }
        refs_url = strbuf_detach(&buffer, NULL);
 
-       http_ret = http_get_strbuf(refs_url, &type, &buffer, HTTP_NO_CACHE);
+       http_ret = http_get_strbuf(refs_url, &type, &buffer,
+                                  HTTP_NO_CACHE | HTTP_KEEP_ERROR);
        switch (http_ret) {
        case HTTP_OK:
                break;
        case HTTP_MISSING_TARGET:
-               die("%s not found: did you run git update-server-info on the"
-                   " server?", refs_url);
+               show_http_message(&type, &buffer);
+               die("repository '%s' not found", url);
        case HTTP_NOAUTH:
-               die("Authentication failed");
+               show_http_message(&type, &buffer);
+               die("Authentication failed for '%s'", url);
        default:
-               http_error(refs_url, http_ret);
-               die("HTTP request failed");
+               show_http_message(&type, &buffer);
+               die("unable to access '%s': %s", url, curl_errorstr);
        }
 
        last= xcalloc(1, sizeof(*last_discovery));
@@ -138,32 +248,35 @@ static struct discovery* discover_refs(const char *service)
        if (maybe_smart &&
            (5 <= last->len && last->buf[4] == '#') &&
            !strbuf_cmp(&exp, &type)) {
+               char *line;
+
                /*
                 * smart HTTP response; validate that the service
                 * pkt-line matches our request.
                 */
-               if (packet_get_line(&buffer, &last->buf, &last->len) <= 0)
-                       die("%s has invalid packet header", refs_url);
-               if (buffer.len && buffer.buf[buffer.len - 1] == '\n')
-                       strbuf_setlen(&buffer, buffer.len - 1);
+               line = packet_read_line_buf(&last->buf, &last->len, NULL);
 
                strbuf_reset(&exp);
                strbuf_addf(&exp, "# service=%s", service);
-               if (strbuf_cmp(&exp, &buffer))
-                       die("invalid server response; got '%s'", buffer.buf);
+               if (strcmp(line, exp.buf))
+                       die("invalid server response; got '%s'", line);
                strbuf_release(&exp);
 
                /* The header can include additional metadata lines, up
                 * until a packet flush marker.  Ignore these now, but
                 * in the future we might start to scan them.
                 */
-               strbuf_reset(&buffer);
-               while (packet_get_line(&buffer, &last->buf, &last->len) > 0)
-                       strbuf_reset(&buffer);
+               while (packet_read_line_buf(&last->buf, &last->len, NULL))
+                       ;
 
                last->proto_git = 1;
        }
 
+       if (last->proto_git)
+               last->refs = parse_git_refs(last, for_push);
+       else
+               last->refs = parse_info_refs(last);
+
        free(refs_url);
        strbuf_release(&exp);
        strbuf_release(&type);
@@ -172,99 +285,16 @@ static struct discovery* discover_refs(const char *service)
        return last;
 }
 
-static int write_discovery(int in, int out, void *data)
-{
-       struct discovery *heads = data;
-       int err = 0;
-       if (write_in_full(out, heads->buf, heads->len) != heads->len)
-               err = 1;
-       close(out);
-       return err;
-}
-
-static struct ref *parse_git_refs(struct discovery *heads, int for_push)
-{
-       struct ref *list = NULL;
-       struct async async;
-
-       memset(&async, 0, sizeof(async));
-       async.proc = write_discovery;
-       async.data = heads;
-       async.out = -1;
-
-       if (start_async(&async))
-               die("cannot start thread to parse advertised refs");
-       get_remote_heads(async.out, &list,
-                       for_push ? REF_NORMAL : 0, NULL);
-       close(async.out);
-       if (finish_async(&async))
-               die("ref parsing thread failed");
-       return list;
-}
-
-static struct ref *parse_info_refs(struct discovery *heads)
-{
-       char *data, *start, *mid;
-       char *ref_name;
-       int i = 0;
-
-       struct ref *refs = NULL;
-       struct ref *ref = NULL;
-       struct ref *last_ref = NULL;
-
-       data = heads->buf;
-       start = NULL;
-       mid = data;
-       while (i < heads->len) {
-               if (!start) {
-                       start = &data[i];
-               }
-               if (data[i] == '\t')
-                       mid = &data[i];
-               if (data[i] == '\n') {
-                       if (mid - start != 40)
-                               die("%sinfo/refs not valid: is this a git repository?", url);
-                       data[i] = 0;
-                       ref_name = mid + 1;
-                       ref = xmalloc(sizeof(struct ref) +
-                                     strlen(ref_name) + 1);
-                       memset(ref, 0, sizeof(struct ref));
-                       strcpy(ref->name, ref_name);
-                       get_sha1_hex(start, ref->old_sha1);
-                       if (!refs)
-                               refs = ref;
-                       if (last_ref)
-                               last_ref->next = ref;
-                       last_ref = ref;
-                       start = NULL;
-               }
-               i++;
-       }
-
-       ref = alloc_ref("HEAD");
-       if (!http_fetch_ref(url, ref) &&
-           !resolve_remote_symref(ref, refs)) {
-               ref->next = refs;
-               refs = ref;
-       } else {
-               free(ref);
-       }
-
-       return refs;
-}
-
 static struct ref *get_refs(int for_push)
 {
        struct discovery *heads;
 
        if (for_push)
-               heads = discover_refs("git-receive-pack");
+               heads = discover_refs("git-receive-pack", for_push);
        else
-               heads = discover_refs("git-upload-pack");
+               heads = discover_refs("git-upload-pack", for_push);
 
-       if (heads->proto_git)
-               return parse_git_refs(heads, for_push);
-       return parse_info_refs(heads);
+       return heads->refs;
 }
 
 static void output_refs(struct ref *refs)
@@ -278,7 +308,6 @@ static void output_refs(struct ref *refs)
        }
        printf("\n");
        fflush(stdout);
-       free_refs(refs);
 }
 
 struct rpc_state {
@@ -308,7 +337,7 @@ static size_t rpc_out(void *ptr, size_t eltsize,
 
        if (!avail) {
                rpc->initial_buffer = 0;
-               avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+               avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0);
                if (!avail)
                        return 0;
                rpc->pos = 0;
@@ -425,7 +454,7 @@ static int post_rpc(struct rpc_state *rpc)
                        break;
                }
 
-               n = packet_read_line(rpc->out, buf, left);
+               n = packet_read(rpc->out, NULL, NULL, buf, left, 0);
                if (!n)
                        break;
                rpc->len += n;
@@ -579,7 +608,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
        rpc->hdr_accept = strbuf_detach(&buf, NULL);
 
        while (!err) {
-               int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+               int n = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0);
                if (!n)
                        break;
                rpc->pos = 0;
@@ -644,7 +673,7 @@ static int fetch_git(struct discovery *heads,
        struct strbuf preamble = STRBUF_INIT;
        char *depth_arg = NULL;
        int argc = 0, i, err;
-       const char *argv[15];
+       const char *argv[16];
 
        argv[argc++] = "fetch-pack";
        argv[argc++] = "--stateless-rpc";
@@ -658,6 +687,8 @@ static int fetch_git(struct discovery *heads,
                argv[argc++] = "-v";
                argv[argc++] = "-v";
        }
+       if (options.check_self_contained_and_connected)
+               argv[argc++] = "--check-self-contained-and-connected";
        if (!options.progress)
                argv[argc++] = "--no-progress";
        if (options.depth) {
@@ -685,7 +716,7 @@ static int fetch_git(struct discovery *heads,
 
        err = rpc_service(&rpc, heads);
        if (rpc.result.len)
-               safe_write(1, rpc.result.buf, rpc.result.len);
+               write_or_die(1, rpc.result.buf, rpc.result.len);
        strbuf_release(&rpc.result);
        strbuf_release(&preamble);
        free(depth_arg);
@@ -694,7 +725,7 @@ static int fetch_git(struct discovery *heads,
 
 static int fetch(int nr_heads, struct ref **to_fetch)
 {
-       struct discovery *d = discover_refs("git-upload-pack");
+       struct discovery *d = discover_refs("git-upload-pack", 0);
        if (d->proto_git)
                return fetch_git(d, nr_heads, to_fetch);
        else
@@ -778,42 +809,44 @@ static int push_dav(int nr_spec, char **specs)
 static int push_git(struct discovery *heads, int nr_spec, char **specs)
 {
        struct rpc_state rpc;
-       const char **argv;
-       int argc = 0, i, err;
+       int i, err;
+       struct argv_array args;
+       struct string_list_item *cas_option;
+
+       argv_array_init(&args);
+       argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status",
+                        NULL);
 
-       argv = xmalloc((10 + nr_spec) * sizeof(char*));
-       argv[argc++] = "send-pack";
-       argv[argc++] = "--stateless-rpc";
-       argv[argc++] = "--helper-status";
        if (options.thin)
-               argv[argc++] = "--thin";
+               argv_array_push(&args, "--thin");
        if (options.dry_run)
-               argv[argc++] = "--dry-run";
+               argv_array_push(&args, "--dry-run");
        if (options.verbosity == 0)
-               argv[argc++] = "--quiet";
+               argv_array_push(&args, "--quiet");
        else if (options.verbosity > 1)
-               argv[argc++] = "--verbose";
-       argv[argc++] = options.progress ? "--progress" : "--no-progress";
-       argv[argc++] = url;
+               argv_array_push(&args, "--verbose");
+       argv_array_push(&args, options.progress ? "--progress" : "--no-progress");
+       for_each_string_list_item(cas_option, &cas_options)
+               argv_array_push(&args, cas_option->string);
+       argv_array_push(&args, url);
        for (i = 0; i < nr_spec; i++)
-               argv[argc++] = specs[i];
-       argv[argc++] = NULL;
+               argv_array_push(&args, specs[i]);
 
        memset(&rpc, 0, sizeof(rpc));
        rpc.service_name = "git-receive-pack",
-       rpc.argv = argv;
+       rpc.argv = args.argv;
 
        err = rpc_service(&rpc, heads);
        if (rpc.result.len)
-               safe_write(1, rpc.result.buf, rpc.result.len);
+               write_or_die(1, rpc.result.buf, rpc.result.len);
        strbuf_release(&rpc.result);
-       free(argv);
+       argv_array_clear(&args);
        return err;
 }
 
 static int push(int nr_spec, char **specs)
 {
-       struct discovery *heads = discover_refs("git-receive-pack");
+       struct discovery *heads = discover_refs("git-receive-pack", 1);
        int ret;
 
        if (heads->proto_git)
@@ -930,6 +963,7 @@ int main(int argc, const char **argv)
                        printf("fetch\n");
                        printf("option\n");
                        printf("push\n");
+                       printf("check-connectivity\n");
                        printf("\n");
                        fflush(stdout);
                } else {