diff options
Diffstat (limited to 'remote-curl.c')
-rw-r--r-- | remote-curl.c | 595 |
1 files changed, 366 insertions, 229 deletions
diff --git a/remote-curl.c b/remote-curl.c index 775d614303..dd63bc27ab 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -6,20 +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; /* always ends with a trailing slash */ +/* 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 struct string_list cas_options = STRING_LIST_INIT_DUP; static int set_option(const char *name, const char *value) { @@ -66,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 */; } } @@ -76,67 +125,159 @@ 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 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, &heads->shallow); + 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.buf); + 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.buf, 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->shallow.sha1); 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 *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; - char *refs_url; - int http_ret, is_http = 0, proto_git_candidate = 1; + int http_ret, maybe_smart = 0; + struct http_get_options options; if (last && !strcmp(service, last->service)) return last; free_discovery(last); - strbuf_addf(&buffer, "%sinfo/refs", url); - if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) { - is_http = 1; - if (!strchr(url, '?')) - strbuf_addch(&buffer, '?'); + 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(&buffer, '&'); - strbuf_addf(&buffer, "service=%s", service); + strbuf_addch(&refs_url, '&'); + strbuf_addf(&refs_url, "service=%s", service); } - refs_url = strbuf_detach(&buffer, NULL); - http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); - - /* try again with "plain" url (no ? or & appended) */ - if (http_ret != HTTP_OK) { - free(refs_url); - strbuf_reset(&buffer); - - proto_git_candidate = 0; - strbuf_addf(&buffer, "%sinfo/refs", url); - refs_url = strbuf_detach(&buffer, NULL); - - http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); - } + 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: - die("%s not found: did you run git update-server-info on the" - " server?", refs_url); + show_http_message(&type, &charset, &buffer); + die("repository '%s' not found", url.buf); case HTTP_NOAUTH: - die("Authentication failed"); + show_http_message(&type, &charset, &buffer); + die("Authentication failed for '%s'", url.buf); default: - http_error(refs_url, http_ret); - die("HTTP request failed"); + show_http_message(&type, &charset, &buffer); + die("unable to access '%s': %s", url.buf, curl_errorstr); } last= xcalloc(1, sizeof(*last_discovery)); @@ -144,130 +285,59 @@ static struct discovery* discover_refs(const char *service) last->buf_alloc = strbuf_detach(&buffer, &last->len); last->buf = last->buf_alloc; - if (is_http && proto_git_candidate - && 5 <= last->len && last->buf[4] == '#') { - /* smart HTTP response; validate that the service + 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. */ - 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); + 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; } - free(refs_url); + 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 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) -{ - 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, 0, NULL, 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') { - 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); - return parse_info_refs(heads); + return heads->refs; } static void output_refs(struct ref *refs) @@ -281,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; @@ -310,7 +380,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; @@ -338,7 +408,7 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) rpc->pos = 0; return CURLIOE_OK; } - fprintf(stderr, "Unable to rewind rpc post data - try increasing http.postBuffer\n"); + error("unable to rewind rpc post data - try increasing http.postBuffer"); return CURLIOE_FAILRESTART; default: @@ -347,7 +417,7 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) } #endif -static size_t rpc_in(const void *ptr, size_t eltsize, +static size_t rpc_in(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; @@ -356,24 +426,26 @@ static size_t rpc_in(const void *ptr, size_t eltsize, return size; } -static int run_slot(struct active_request_slot *slot) +static int run_slot(struct active_request_slot *slot, + struct slot_results *results) { - int err = 0; - struct slot_results results; + int err; + struct slot_results results_buf; - slot->results = &results; - slot->curl_result = curl_easy_perform(slot->curl); - finish_active_slot(slot); + if (!results) + results = &results_buf; - if (results.curl_result != CURLE_OK) { - err |= error("RPC failed; result=%d, HTTP code = %ld", - results.curl_result, results.http_code); + 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) +static int probe_rpc(struct rpc_state *rpc, struct slot_results *results) { struct active_request_slot *slot; struct curl_slist *headers = NULL; @@ -388,14 +460,14 @@ static int probe_rpc(struct rpc_state *rpc) 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, ""); + 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); + err = run_slot(slot, results); curl_slist_free_all(headers); strbuf_release(&buf); @@ -408,7 +480,9 @@ static int post_rpc(struct rpc_state *rpc) struct curl_slist *headers = NULL; 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 @@ -425,28 +499,39 @@ 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; } if (large_request) { - err = probe_rpc(rpc); - if (err) - return err; + 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; + + 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, ""); - - headers = curl_slist_append(headers, rpc->hdr_content_type); - headers = curl_slist_append(headers, rpc->hdr_accept); - headers = curl_slist_append(headers, "Expect:"); + curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip"); if (large_request) { /* The request body is large and the size cannot be predicted. @@ -465,47 +550,51 @@ static int post_rpc(struct rpc_state *rpc) 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. */ - size_t size; - z_stream stream; + git_zstream stream; int ret; memset(&stream, 0, sizeof(stream)); - ret = deflateInit2(&stream, Z_BEST_COMPRESSION, - Z_DEFLATED, (15 + 16), - 8, Z_DEFAULT_STRATEGY); - if (ret != Z_OK) - die("cannot deflate request; zlib init error %d", ret); - size = deflateBound(&stream, rpc->len); - gzip_body = xmalloc(size); + 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 = size; + stream.avail_out = gzip_size; - ret = deflate(&stream, Z_FINISH); + ret = git_deflate(&stream, Z_FINISH); if (ret != Z_STREAM_END) die("cannot deflate request; zlib deflate error %d", ret); - ret = deflateEnd(&stream); + ret = git_deflate_end_gently(&stream); if (ret != Z_OK) die("cannot deflate request; zlib end error %d", ret); - size = stream.total_out; + 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, size); + 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)size); + (unsigned long)rpc->len, (unsigned long)gzip_size); fflush(stderr); } } else { @@ -525,7 +614,13 @@ 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); - err = run_slot(slot); + 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); @@ -536,16 +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; - 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); @@ -555,7 +652,7 @@ 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); @@ -565,7 +662,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; @@ -575,7 +672,14 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) close(client.in); client.in = -1; - strbuf_read(&rpc->result, client.out, 0); + 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; @@ -600,7 +704,7 @@ 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)); - walker = get_http_walker(url); + walker = get_http_walker(url.buf); walker->get_all = 1; walker->get_tree = 1; walker->get_history = 1; @@ -613,20 +717,21 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) 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"; @@ -636,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) { @@ -644,32 +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 || !*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 @@ -684,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]; @@ -739,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; @@ -753,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) @@ -799,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); } @@ -811,19 +939,22 @@ 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) @@ -834,7 +965,7 @@ int main(int argc, const char **argv) git_extract_argv0_path(argv[0]); setup_git_directory_gently(&nongit); if (argc < 2) { - fprintf(stderr, "Remote needed\n"); + error("remote-curl: usage: git remote-curl <remote> [<url>]"); return 1; } @@ -845,33 +976,37 @@ int main(int argc, const char **argv) remote = remote_get(argv[1]); if (argc > 2) { - end_url_with_slash(&buf, argv[2]); + end_url_with_slash(&url, argv[2]); } else { - end_url_with_slash(&buf, remote->url[0]); + end_url_with_slash(&url, remote->url[0]); } - url = strbuf_detach(&buf, NULL); - - http_init(remote); + 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("Fetch attempted without a local repo"); + 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) @@ -879,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) @@ -892,9 +1027,11 @@ 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); |