diff options
Diffstat (limited to 'remote-curl.c')
-rw-r--r-- | remote-curl.c | 681 |
1 files changed, 481 insertions, 200 deletions
diff --git a/remote-curl.c b/remote-curl.c index 38cab05ec..515ac9b41 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -6,27 +6,30 @@ #include "exec_cmd.h" #include "run-command.h" #include "pkt-line.h" +#include "string-list.h" #include "sideband.h" +#include "argv-array.h" +#include "credential.h" +#include "sha1-array.h" static struct remote *remote; -static const char *url; -static struct walker *walker; +/* always ends with a trailing slash */ +static struct strbuf url = STRBUF_INIT; struct options { int verbosity; unsigned long depth; unsigned progress : 1, + check_self_contained_and_connected : 1, + cloning : 1, + update_shallow : 1, followtags : 1, dry_run : 1, - thin : 1; + thin : 1, + push_cert : 1; }; static struct options options; - -static void init_walker(void) -{ - if (!walker) - walker = get_http_walker(url, remote); -} +static struct string_list cas_options = STRING_LIST_INIT_DUP; static int set_option(const char *name, const char *value) { @@ -73,7 +76,46 @@ static int set_option(const char *name, const char *value) return -1; return 0; } - else { + 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 if (!strcmp(name, "cloning")) { + if (!strcmp(value, "true")) + options.cloning = 1; + else if (!strcmp(value, "false")) + options.cloning = 0; + else + return -1; + return 0; + } else if (!strcmp(name, "update-shallow")) { + if (!strcmp(value, "true")) + options.update_shallow = 1; + else if (!strcmp(value, "false")) + options.update_shallow = 0; + else + return -1; + return 0; + } else if (!strcmp(name, "pushcert")) { + if (!strcmp(value, "true")) + options.push_cert = 1; + else if (!strcmp(value, "false")) + options.push_cert = 0; + else + return -1; + return 0; + } else { return 1 /* unsupported */; } } @@ -83,118 +125,17 @@ struct discovery { char *buf_alloc; char *buf; size_t len; + struct ref *refs; + struct sha1_array shallow; unsigned proto_git : 1; }; static struct discovery *last_discovery; -static void free_discovery(struct discovery *d) -{ - if (d) { - if (d == last_discovery) - last_discovery = NULL; - free(d->buf_alloc); - free(d); - } -} - -static struct discovery* discover_refs(const char *service) -{ - struct strbuf buffer = STRBUF_INIT; - struct discovery *last = last_discovery; - char *refs_url; - int http_ret, is_http = 0; - - if (last && !strcmp(service, last->service)) - return last; - free_discovery(last); - - strbuf_addf(&buffer, "%s/info/refs", url); - if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) { - is_http = 1; - if (!strchr(url, '?')) - strbuf_addch(&buffer, '?'); - else - strbuf_addch(&buffer, '&'); - strbuf_addf(&buffer, "service=%s", service); - } - refs_url = strbuf_detach(&buffer, NULL); - - init_walker(); - http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); - 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); - default: - http_error(refs_url, http_ret); - die("HTTP request failed"); - } - - last= xcalloc(1, sizeof(*last_discovery)); - last->service = service; - last->buf_alloc = strbuf_detach(&buffer, &last->len); - last->buf = last->buf_alloc; - - if (is_http && 5 <= last->len && last->buf[4] == '#') { - /* smart HTTP response; validate that the service - * pkt-line matches our request. - */ - struct strbuf exp = STRBUF_INIT; - - 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); - - strbuf_addf(&exp, "# service=%s", service); - if (strbuf_cmp(&exp, &buffer)) - die("invalid server response; got '%s'", buffer.buf); - 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); - - last->proto_git = 1; - } - - free(refs_url); - strbuf_release(&buffer); - last_discovery = last; - return last; -} - -static int write_discovery(int fd, void *data) -{ - struct discovery *heads = data; - int err = 0; - if (write_in_full(fd, heads->buf, heads->len) != heads->len) - err = 1; - close(fd); - return err; -} - -static struct ref *parse_git_refs(struct discovery *heads) +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; - - if (start_async(&async)) - die("cannot start thread to parse advertised refs"); - get_remote_heads(async.out, &list, 0, NULL, 0, NULL); - close(async.out); - if (finish_async(&async)) - die("ref parsing thread failed"); + get_remote_heads(-1, heads->buf, heads->len, &list, + for_push ? REF_NORMAL : 0, NULL, &heads->shallow); return list; } @@ -218,6 +159,9 @@ static struct ref *parse_info_refs(struct discovery *heads) 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.buf); data[i] = 0; ref_name = mid + 1; ref = xmalloc(sizeof(struct ref) + @@ -235,9 +179,8 @@ static struct ref *parse_info_refs(struct discovery *heads) i++; } - init_walker(); ref = alloc_ref("HEAD"); - if (!walker->fetch_ref(walker, ref) && + if (!http_fetch_ref(url.buf, ref) && !resolve_remote_symref(ref, refs)) { ref->next = refs; refs = ref; @@ -248,18 +191,153 @@ static struct ref *parse_info_refs(struct discovery *heads) return refs; } +static void free_discovery(struct discovery *d) +{ + if (d) { + if (d == last_discovery) + last_discovery = NULL; + free(d->shallow.sha1); + free(d->buf_alloc); + free_refs(d->refs); + free(d); + } +} + +static int show_http_message(struct strbuf *type, struct strbuf *charset, + 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. + */ + if (strcmp(type->buf, "text/plain")) + return -1; + if (charset->len) + strbuf_reencode(msg, charset->buf, get_log_output_encoding()); + + 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; + struct strbuf charset = STRBUF_INIT; + struct strbuf buffer = STRBUF_INIT; + struct strbuf refs_url = STRBUF_INIT; + struct strbuf effective_url = STRBUF_INIT; + struct discovery *last = last_discovery; + int http_ret, maybe_smart = 0; + struct http_get_options options; + + if (last && !strcmp(service, last->service)) + return last; + free_discovery(last); + + strbuf_addf(&refs_url, "%sinfo/refs", url.buf); + if ((starts_with(url.buf, "http://") || starts_with(url.buf, "https://")) && + git_env_bool("GIT_SMART_HTTP", 1)) { + maybe_smart = 1; + if (!strchr(url.buf, '?')) + strbuf_addch(&refs_url, '?'); + else + strbuf_addch(&refs_url, '&'); + strbuf_addf(&refs_url, "service=%s", service); + } + + memset(&options, 0, sizeof(options)); + options.content_type = &type; + options.charset = &charset; + options.effective_url = &effective_url; + options.base_url = &url; + options.no_cache = 1; + options.keep_error = 1; + + http_ret = http_get_strbuf(refs_url.buf, &buffer, &options); + switch (http_ret) { + case HTTP_OK: + break; + case HTTP_MISSING_TARGET: + show_http_message(&type, &charset, &buffer); + die("repository '%s' not found", url.buf); + case HTTP_NOAUTH: + show_http_message(&type, &charset, &buffer); + die("Authentication failed for '%s'", url.buf); + default: + show_http_message(&type, &charset, &buffer); + die("unable to access '%s': %s", url.buf, curl_errorstr); + } + + last= xcalloc(1, sizeof(*last_discovery)); + last->service = service; + last->buf_alloc = strbuf_detach(&buffer, &last->len); + last->buf = last->buf_alloc; + + strbuf_addf(&exp, "application/x-%s-advertisement", 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. + */ + line = packet_read_line_buf(&last->buf, &last->len, NULL); + + strbuf_reset(&exp); + strbuf_addf(&exp, "# service=%s", service); + 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. + */ + 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); + + strbuf_release(&refs_url); + strbuf_release(&exp); + strbuf_release(&type); + strbuf_release(&charset); + strbuf_release(&effective_url); + strbuf_release(&buffer); + last_discovery = last; + return last; +} + 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); - return parse_info_refs(heads); + return heads->refs; } static void output_refs(struct ref *refs) @@ -273,12 +351,12 @@ static void output_refs(struct ref *refs) } printf("\n"); fflush(stdout); - free_refs(refs); } struct rpc_state { const char *service_name; const char **argv; + struct strbuf *stdin_preamble; char *service_url; char *hdr_content_type; char *hdr_accept; @@ -289,6 +367,8 @@ struct rpc_state { int in; int out; struct strbuf result; + unsigned gzip_request : 1; + unsigned initial_buffer : 1; }; static size_t rpc_out(void *ptr, size_t eltsize, @@ -299,21 +379,45 @@ static size_t rpc_out(void *ptr, size_t eltsize, size_t avail = rpc->len - rpc->pos; if (!avail) { - avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + rpc->initial_buffer = 0; + avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0); if (!avail) return 0; rpc->pos = 0; rpc->len = avail; } - if (max < avail); + if (max < avail) avail = max; memcpy(ptr, rpc->buf + rpc->pos, avail); rpc->pos += avail; return avail; } -static size_t rpc_in(const void *ptr, size_t eltsize, +#ifndef NO_CURL_IOCTL +static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) +{ + struct rpc_state *rpc = clientp; + + switch (cmd) { + case CURLIOCMD_NOP: + return CURLIOE_OK; + + case CURLIOCMD_RESTARTREAD: + if (rpc->initial_buffer) { + rpc->pos = 0; + return CURLIOE_OK; + } + error("unable to rewind rpc post data - try increasing http.postBuffer"); + return CURLIOE_FAILRESTART; + + default: + return CURLIOE_UNKNOWNCMD; + } +} +#endif + +static size_t rpc_in(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; @@ -322,12 +426,63 @@ static size_t rpc_in(const void *ptr, size_t eltsize, return size; } +static int run_slot(struct active_request_slot *slot, + struct slot_results *results) +{ + int err; + struct slot_results results_buf; + + if (!results) + results = &results_buf; + + err = run_one_slot(slot, results); + + if (err != HTTP_OK && err != HTTP_REAUTH) { + error("RPC failed; result=%d, HTTP code = %ld", + results->curl_result, results->http_code); + } + + return err; +} + +static int probe_rpc(struct rpc_state *rpc, struct slot_results *results) +{ + struct active_request_slot *slot; + struct curl_slist *headers = NULL; + struct strbuf buf = STRBUF_INIT; + int err; + + slot = get_active_slot(); + + headers = curl_slist_append(headers, rpc->hdr_content_type); + headers = curl_slist_append(headers, rpc->hdr_accept); + + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_POST, 1); + curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url); + curl_easy_setopt(slot->curl, CURLOPT_ENCODING, NULL); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, "0000"); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, 4); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); + curl_easy_setopt(slot->curl, CURLOPT_FILE, &buf); + + err = run_slot(slot, results); + + curl_slist_free_all(headers); + strbuf_release(&buf); + return err; +} + static int post_rpc(struct rpc_state *rpc) { struct active_request_slot *slot; - struct slot_results results; struct curl_slist *headers = NULL; - int err = 0, large_request = 0; + int use_gzip = rpc->gzip_request; + char *gzip_body = NULL; + size_t gzip_size = 0; + int err, large_request = 0; + int needs_100_continue = 0; /* Try to load the entire request, if we can fit it into the * allocated buffer space we can use HTTP/1.0 and avoid the @@ -340,38 +495,108 @@ static int post_rpc(struct rpc_state *rpc) if (left < LARGE_PACKET_MAX) { large_request = 1; + use_gzip = 0; 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; } - slot = get_active_slot(); - slot->results = &results; + if (large_request) { + struct slot_results results; + + do { + err = probe_rpc(rpc, &results); + if (err == HTTP_REAUTH) + credential_fill(&http_auth); + } while (err == HTTP_REAUTH); + if (err != HTTP_OK) + return -1; - curl_easy_setopt(slot->curl, CURLOPT_POST, 1); - curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); - curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url); + if (results.auth_avail & CURLAUTH_GSSNEGOTIATE) + needs_100_continue = 1; + } headers = curl_slist_append(headers, rpc->hdr_content_type); headers = curl_slist_append(headers, rpc->hdr_accept); + headers = curl_slist_append(headers, needs_100_continue ? + "Expect: 100-continue" : "Expect:"); + +retry: + slot = get_active_slot(); + + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_POST, 1); + curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url); + curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip"); if (large_request) { /* The request body is large and the size cannot be predicted. * We must use chunked encoding to send it. */ - headers = curl_slist_append(headers, "Expect: 100-continue"); headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); + rpc->initial_buffer = 1; curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out); curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl); + curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc); +#endif if (options.verbosity > 1) { fprintf(stderr, "POST %s (chunked)\n", rpc->service_name); fflush(stderr); } + } else if (gzip_body) { + /* + * If we are looping to retry authentication, then the previous + * run will have set up the headers and gzip buffer already, + * and we just need to send it. + */ + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, gzip_size); + + } else if (use_gzip && 1024 < rpc->len) { + /* The client backend isn't giving us compressed data so + * we can try to deflate it ourselves, this may save on. + * the transfer time. + */ + git_zstream stream; + int ret; + + memset(&stream, 0, sizeof(stream)); + git_deflate_init_gzip(&stream, Z_BEST_COMPRESSION); + gzip_size = git_deflate_bound(&stream, rpc->len); + gzip_body = xmalloc(gzip_size); + + stream.next_in = (unsigned char *)rpc->buf; + stream.avail_in = rpc->len; + stream.next_out = (unsigned char *)gzip_body; + stream.avail_out = gzip_size; + + ret = git_deflate(&stream, Z_FINISH); + if (ret != Z_STREAM_END) + die("cannot deflate request; zlib deflate error %d", ret); + + ret = git_deflate_end_gently(&stream); + if (ret != Z_OK) + die("cannot deflate request; zlib end error %d", ret); + + gzip_size = stream.total_out; + + headers = curl_slist_append(headers, "Content-Encoding: gzip"); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, gzip_size); + + if (options.verbosity > 1) { + fprintf(stderr, "POST %s (gzip %lu to %lu bytes)\n", + rpc->service_name, + (unsigned long)rpc->len, (unsigned long)gzip_size); + fflush(stderr); + } } else { /* We know the complete request size in advance, use the * more normal Content-Length approach. @@ -389,15 +614,16 @@ static int post_rpc(struct rpc_state *rpc) curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in); curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc); - slot->curl_result = curl_easy_perform(slot->curl); - finish_active_slot(slot); - - if (results.curl_result != CURLE_OK) { - err |= error("RPC failed; result=%d, HTTP code = %ld", - results.curl_result, results.http_code); + err = run_slot(slot, NULL); + if (err == HTTP_REAUTH && !large_request) { + credential_fill(&http_auth); + goto retry; } + if (err != HTTP_OK) + err = -1; curl_slist_free_all(headers); + free(gzip_body); return err; } @@ -405,17 +631,18 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) { const char *svc = rpc->service_name; struct strbuf buf = STRBUF_INIT; - struct child_process client; + struct strbuf *preamble = rpc->stdin_preamble; + struct child_process client = CHILD_PROCESS_INIT; int err = 0; - init_walker(); - memset(&client, 0, sizeof(client)); client.in = -1; client.out = -1; client.git_cmd = 1; client.argv = rpc->argv; if (start_command(&client)) exit(1); + if (preamble) + write_or_die(client.in, preamble->buf, preamble->len); if (heads) write_or_die(client.in, heads->buf, heads->len); @@ -425,28 +652,36 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) rpc->out = client.out; strbuf_init(&rpc->result, 0); - strbuf_addf(&buf, "%s/%s", url, svc); + strbuf_addf(&buf, "%s%s", url.buf, svc); rpc->service_url = strbuf_detach(&buf, NULL); strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc); rpc->hdr_content_type = strbuf_detach(&buf, NULL); - strbuf_addf(&buf, "Accept: application/x-%s-response", svc); + strbuf_addf(&buf, "Accept: application/x-%s-result", svc); 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; rpc->len = n; err |= post_rpc(rpc); } - strbuf_read(&rpc->result, client.out, 0); close(client.in); - close(client.out); client.in = -1; + if (!err) { + strbuf_read(&rpc->result, client.out, 0); + } else { + char buf[4096]; + for (;;) + if (xread(client.out, buf, sizeof(buf)) <= 0) + break; + } + + close(client.out); client.out = -1; err |= finish_command(&client); @@ -460,6 +695,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) static int fetch_dumb(int nr_heads, struct ref **to_fetch) { + struct walker *walker; char **targets = xmalloc(nr_heads * sizeof(char*)); int ret, i; @@ -468,32 +704,34 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) for (i = 0; i < nr_heads; i++) targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); - init_walker(); + walker = get_http_walker(url.buf); walker->get_all = 1; walker->get_tree = 1; walker->get_history = 1; walker->get_verbosely = options.verbosity >= 3; walker->get_recover = 0; ret = walker_fetch(walker, nr_heads, targets, NULL, NULL); + walker_free(walker); for (i = 0; i < nr_heads; i++) free(targets[i]); free(targets); - return ret ? error("Fetch failed.") : 0; + return ret ? error("fetch failed.") : 0; } static int fetch_git(struct discovery *heads, int nr_heads, struct ref **to_fetch) { struct rpc_state rpc; + struct strbuf preamble = STRBUF_INIT; char *depth_arg = NULL; - const char **argv; int argc = 0, i, err; + const char *argv[17]; - argv = xmalloc((15 + nr_heads) * sizeof(char*)); argv[argc++] = "fetch-pack"; argv[argc++] = "--stateless-rpc"; + argv[argc++] = "--stdin"; argv[argc++] = "--lock-pack"; if (options.followtags) argv[argc++] = "--include-tag"; @@ -503,6 +741,12 @@ 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.cloning) + argv[argc++] = "--cloning"; + if (options.update_shallow) + argv[argc++] = "--update-shallow"; if (!options.progress) argv[argc++] = "--no-progress"; if (options.depth) { @@ -511,31 +755,36 @@ static int fetch_git(struct discovery *heads, depth_arg = strbuf_detach(&buf, NULL); argv[argc++] = depth_arg; } - argv[argc++] = url; + argv[argc++] = url.buf; + argv[argc++] = NULL; + for (i = 0; i < nr_heads; i++) { struct ref *ref = to_fetch[i]; if (!*ref->name) die("cannot fetch by sha1 over smart http"); - argv[argc++] = ref->name; + packet_buf_write(&preamble, "%s %s\n", + sha1_to_hex(ref->old_sha1), ref->name); } - argv[argc++] = NULL; + packet_buf_flush(&preamble); memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-upload-pack", rpc.argv = argv; + rpc.stdin_preamble = &preamble; + rpc.gzip_request = 1; 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); + strbuf_release(&preamble); free(depth_arg); return err; } 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 @@ -550,9 +799,9 @@ static void parse_fetch(struct strbuf *buf) int alloc_heads = 0, nr_heads = 0; do { - if (!prefixcmp(buf->buf, "fetch ")) { - char *p = buf->buf + strlen("fetch "); - char *name; + const char *p; + if (skip_prefix(buf->buf, "fetch ", &p)) { + const char *name; struct ref *ref; unsigned char old_sha1[20]; @@ -605,7 +854,7 @@ static int push_dav(int nr_spec, char **specs) argv[argc++] = "--dry-run"; if (options.verbosity > 1) argv[argc++] = "--verbose"; - argv[argc++] = url; + argv[argc++] = url.buf; for (i = 0; i < nr_spec; i++) argv[argc++] = specs[i]; argv[argc++] = NULL; @@ -619,39 +868,52 @@ 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; + struct strbuf preamble = STRBUF_INIT; + + 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"; - if (options.verbosity > 1) - argv[argc++] = "--verbose"; - argv[argc++] = url; + argv_array_push(&args, "--dry-run"); + if (options.push_cert) + argv_array_push(&args, "--signed"); + if (options.verbosity == 0) + argv_array_push(&args, "--quiet"); + else if (options.verbosity > 1) + 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.buf); + + argv_array_push(&args, "--stdin"); for (i = 0; i < nr_spec; i++) - argv[argc++] = specs[i]; - argv[argc++] = NULL; + packet_buf_write(&preamble, "%s\n", specs[i]); + packet_buf_flush(&preamble); memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-receive-pack", - rpc.argv = argv; + rpc.argv = args.argv; + rpc.stdin_preamble = &preamble; 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); + strbuf_release(&preamble); + 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) @@ -665,10 +927,10 @@ static int push(int nr_spec, char **specs) static void parse_push(struct strbuf *buf) { char **specs = NULL; - int alloc_spec = 0, nr_spec = 0, i; + int alloc_spec = 0, nr_spec = 0, i, ret; do { - if (!prefixcmp(buf->buf, "push ")) { + if (starts_with(buf->buf, "push ")) { ALLOC_GROW(specs, nr_spec + 1, alloc_spec); specs[nr_spec++] = xstrdup(buf->buf + 5); } @@ -677,29 +939,33 @@ static void parse_push(struct strbuf *buf) strbuf_reset(buf); if (strbuf_getline(buf, stdin, '\n') == EOF) - return; + goto free_specs; if (!*buf->buf) break; } while (1); - if (push(nr_spec, specs)) + ret = push(nr_spec, specs); + printf("\n"); + fflush(stdout); + + if (ret) exit(128); /* error already reported */ + + free_specs: for (i = 0; i < nr_spec; i++) free(specs[i]); free(specs); - - printf("\n"); - fflush(stdout); } int main(int argc, const char **argv) { struct strbuf buf = STRBUF_INIT; + int nongit; git_extract_argv0_path(argv[0]); - setup_git_directory(); + setup_git_directory_gently(&nongit); if (argc < 2) { - fprintf(stderr, "Remote needed\n"); + error("remote-curl: usage: git remote-curl <remote> [<url>]"); return 1; } @@ -710,27 +976,37 @@ int main(int argc, const char **argv) remote = remote_get(argv[1]); if (argc > 2) { - url = argv[2]; + end_url_with_slash(&url, argv[2]); } else { - url = remote->url[0]; + end_url_with_slash(&url, remote->url[0]); } + http_init(remote, url.buf, 0); + do { - if (strbuf_getline(&buf, stdin, '\n') == EOF) + const char *arg; + + if (strbuf_getline(&buf, stdin, '\n') == EOF) { + if (ferror(stdin)) + error("remote-curl: error reading command stream from git"); + return 1; + } + if (buf.len == 0) break; - if (!prefixcmp(buf.buf, "fetch ")) { + if (starts_with(buf.buf, "fetch ")) { + if (nongit) + die("remote-curl: fetch attempted without a local repo"); parse_fetch(&buf); - } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) { + } else if (!strcmp(buf.buf, "list") || starts_with(buf.buf, "list ")) { int for_push = !!strstr(buf.buf + 4, "for-push"); output_refs(get_refs(for_push)); - } else if (!prefixcmp(buf.buf, "push ")) { + } else if (starts_with(buf.buf, "push ")) { parse_push(&buf); - } else if (!prefixcmp(buf.buf, "option ")) { - char *name = buf.buf + strlen("option "); - char *value = strchr(name, ' '); + } else if (skip_prefix(buf.buf, "option ", &arg)) { + char *value = strchr(arg, ' '); int result; if (value) @@ -738,7 +1014,7 @@ int main(int argc, const char **argv) else value = "true"; - result = set_option(name, value); + result = set_option(arg, value); if (!result) printf("ok\n"); else if (result < 0) @@ -751,12 +1027,17 @@ 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 { + error("remote-curl: unknown command '%s' from git", buf.buf); return 1; } strbuf_reset(&buf); } while (1); + + http_cleanup(); + return 0; } |