]> git.ipfire.org Git - thirdparty/git.git/blobdiff - remote-curl.c
Merge tag 'l10n-2.25.0-rnd1' of git://github.com/git-l10n/git-po
[thirdparty/git.git] / remote-curl.c
index 1220dffcdc57a17476fb2021db5fb1605857ab80..350d92a074ed82a99060cebb1a4a3fb0a98cfd8e 100644 (file)
@@ -16,6 +16,7 @@
 #include "send-pack.h"
 #include "protocol.h"
 #include "quote.h"
+#include "transport.h"
 
 static struct remote *remote;
 /* always ends with a trailing slash */
@@ -39,7 +40,8 @@ struct options {
                push_cert : 2,
                deepen_relative : 1,
                from_promisor : 1,
-               no_dependents : 1;
+               no_dependents : 1,
+               atomic : 1;
 };
 static struct options options;
 static struct string_list cas_options = STRING_LIST_INIT_DUP;
@@ -147,13 +149,21 @@ static int set_option(const char *name, const char *value)
                else
                        return -1;
                return 0;
+       } else if (!strcmp(name, "atomic")) {
+               if (!strcmp(value, "true"))
+                       options.atomic = 1;
+               else if (!strcmp(value, "false"))
+                       options.atomic = 0;
+               else
+                       return -1;
+               return 0;
        } else if (!strcmp(name, "push-option")) {
                if (*value != '"')
                        string_list_append(&options.push_options, value);
                else {
                        struct strbuf unquoted = STRBUF_INIT;
                        if (unquote_c_style(&unquoted, value, NULL) < 0)
-                               die("invalid quoting in push-option value");
+                               die(_("invalid quoting in push-option value: '%s'"), value);
                        string_list_append_nodup(&options.push_options,
                                                 strbuf_detach(&unquoted, NULL));
                }
@@ -204,7 +214,8 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push)
 
        packet_reader_init(&reader, -1, heads->buf, heads->len,
                           PACKET_READ_CHOMP_NEWLINE |
-                          PACKET_READ_GENTLE_ON_EOF);
+                          PACKET_READ_GENTLE_ON_EOF |
+                          PACKET_READ_DIE_ON_ERR_PACKET);
 
        heads->version = discover_version(&reader);
        switch (heads->version) {
@@ -248,9 +259,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);
+                       if (mid - start != the_hash_algo->hexsz)
+                               die(_("%sinfo/refs not valid: is this a git repository?"),
+                                   transport_anonymize_url(url.buf));
                        data[i] = 0;
                        ref_name = mid + 1;
                        ref = alloc_ref(ref_name);
@@ -330,9 +341,63 @@ static int get_protocol_http_header(enum protocol_version version,
        return 0;
 }
 
+static void check_smart_http(struct discovery *d, const char *service,
+                            struct strbuf *type)
+{
+       const char *p;
+       struct packet_reader reader;
+
+       /*
+        * If we don't see x-$service-advertisement, then it's not smart-http.
+        * But once we do, we commit to it and assume any other protocol
+        * violations are hard errors.
+        */
+       if (!skip_prefix(type->buf, "application/x-", &p) ||
+           !skip_prefix(p, service, &p) ||
+           strcmp(p, "-advertisement"))
+               return;
+
+       packet_reader_init(&reader, -1, d->buf, d->len,
+                          PACKET_READ_CHOMP_NEWLINE |
+                          PACKET_READ_DIE_ON_ERR_PACKET);
+       if (packet_reader_read(&reader) != PACKET_READ_NORMAL)
+               die(_("invalid server response; expected service, got flush packet"));
+
+       if (skip_prefix(reader.line, "# service=", &p) && !strcmp(p, service)) {
+               /*
+                * 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.
+                */
+               for (;;) {
+                       packet_reader_read(&reader);
+                       if (reader.pktlen <= 0) {
+                               break;
+                       }
+               }
+
+               /*
+                * v0 smart http; callers expect us to soak up the
+                * service and header packets
+                */
+               d->buf = reader.src_buffer;
+               d->len = reader.src_len;
+               d->proto_git = 1;
+
+       } else if (!strcmp(reader.line, "version 2")) {
+               /*
+                * v2 smart http; do not consume version packet, which will
+                * be handled elsewhere.
+                */
+               d->proto_git = 1;
+
+       } else {
+               die(_("invalid server response; got '%s'"), reader.line);
+       }
+}
+
 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;
@@ -380,7 +445,6 @@ static struct discovery *discover_refs(const char *service, int for_push)
        http_options.extra_headers = &extra_headers;
        http_options.initial_request = 1;
        http_options.no_cache = 1;
-       http_options.keep_error = 1;
 
        http_ret = http_get_strbuf(refs_url.buf, &buffer, &http_options);
        switch (http_ret) {
@@ -388,55 +452,31 @@ static struct discovery *discover_refs(const char *service, int for_push)
                break;
        case HTTP_MISSING_TARGET:
                show_http_message(&type, &charset, &buffer);
-               die("repository '%s' not found", url.buf);
+               die(_("repository '%s' not found"),
+                   transport_anonymize_url(url.buf));
        case HTTP_NOAUTH:
                show_http_message(&type, &charset, &buffer);
-               die("Authentication failed for '%s'", url.buf);
+               die(_("Authentication failed for '%s'"),
+                   transport_anonymize_url(url.buf));
        default:
                show_http_message(&type, &charset, &buffer);
-               die("unable to access '%s': %s", url.buf, curl_errorstr);
+               die(_("unable to access '%s': %s"),
+                   transport_anonymize_url(url.buf), curl_errorstr);
        }
 
-       if (options.verbosity && !starts_with(refs_url.buf, url.buf))
-               warning(_("redirecting to %s"), url.buf);
+       if (options.verbosity && !starts_with(refs_url.buf, url.buf)) {
+               char *u = transport_anonymize_url(url.buf);
+               warning(_("redirecting to %s"), u);
+               free(u);
+       }
 
        last= xcalloc(1, sizeof(*last_discovery));
        last->service = xstrdup(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);
-               if (!line)
-                       die("invalid server response; expected service, got flush packet");
-
-               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;
-       } else if (maybe_smart &&
-                  last->len > 5 && starts_with(last->buf + 4, "version 2")) {
-               last->proto_git = 1;
-       }
+       if (maybe_smart)
+               check_smart_http(last, service, &type);
 
        if (last->proto_git)
                last->refs = parse_git_refs(last, for_push);
@@ -444,7 +484,6 @@ static struct discovery *discover_refs(const char *service, int for_push)
                last->refs = parse_info_refs(last);
 
        strbuf_release(&refs_url);
-       strbuf_release(&exp);
        strbuf_release(&type);
        strbuf_release(&charset);
        strbuf_release(&effective_url);
@@ -482,8 +521,6 @@ static void output_refs(struct ref *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;
@@ -495,25 +532,121 @@ struct rpc_state {
        int in;
        int out;
        int any_written;
-       struct strbuf result;
        unsigned gzip_request : 1;
        unsigned initial_buffer : 1;
+
+       /*
+        * Whenever a pkt-line is read into buf, append the 4 characters
+        * denoting its length before appending the payload.
+        */
+       unsigned write_line_lengths : 1;
+
+       /*
+        * Used by rpc_out; initialize to 0. This is true if a flush has been
+        * read, but the corresponding line length (if write_line_lengths is
+        * true) and EOF have not been sent to libcurl. Since each flush marks
+        * the end of a request, each flush must be completely sent before any
+        * further reading occurs.
+        */
+       unsigned flush_read_but_not_sent : 1;
 };
 
+/*
+ * Appends the result of reading from rpc->out to the string represented by
+ * rpc->buf and rpc->len if there is enough space. Returns 1 if there was
+ * enough space, 0 otherwise.
+ *
+ * If rpc->write_line_lengths is true, appends the line length as a 4-byte
+ * hexadecimal string before appending the result described above.
+ *
+ * Writes the total number of bytes appended into appended.
+ */
+static int rpc_read_from_out(struct rpc_state *rpc, int options,
+                            size_t *appended,
+                            enum packet_read_status *status) {
+       size_t left;
+       char *buf;
+       int pktlen_raw;
+
+       if (rpc->write_line_lengths) {
+               left = rpc->alloc - rpc->len - 4;
+               buf = rpc->buf + rpc->len + 4;
+       } else {
+               left = rpc->alloc - rpc->len;
+               buf = rpc->buf + rpc->len;
+       }
+
+       if (left < LARGE_PACKET_MAX)
+               return 0;
+
+       *status = packet_read_with_status(rpc->out, NULL, NULL, buf,
+                       left, &pktlen_raw, options);
+       if (*status != PACKET_READ_EOF) {
+               *appended = pktlen_raw + (rpc->write_line_lengths ? 4 : 0);
+               rpc->len += *appended;
+       }
+
+       if (rpc->write_line_lengths) {
+               switch (*status) {
+               case PACKET_READ_EOF:
+                       if (!(options & PACKET_READ_GENTLE_ON_EOF))
+                               die(_("shouldn't have EOF when not gentle on EOF"));
+                       break;
+               case PACKET_READ_NORMAL:
+                       set_packet_header(buf - 4, *appended);
+                       break;
+               case PACKET_READ_DELIM:
+                       memcpy(buf - 4, "0001", 4);
+                       break;
+               case PACKET_READ_FLUSH:
+                       memcpy(buf - 4, "0000", 4);
+                       break;
+               }
+       }
+
+       return 1;
+}
+
 static size_t rpc_out(void *ptr, size_t eltsize,
                size_t nmemb, void *buffer_)
 {
        size_t max = eltsize * nmemb;
        struct rpc_state *rpc = buffer_;
        size_t avail = rpc->len - rpc->pos;
+       enum packet_read_status status;
 
        if (!avail) {
                rpc->initial_buffer = 0;
-               avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0);
-               if (!avail)
-                       return 0;
+               rpc->len = 0;
                rpc->pos = 0;
-               rpc->len = avail;
+               if (!rpc->flush_read_but_not_sent) {
+                       if (!rpc_read_from_out(rpc, 0, &avail, &status))
+                               BUG("The entire rpc->buf should be larger than LARGE_PACKET_MAX");
+                       if (status == PACKET_READ_FLUSH)
+                               rpc->flush_read_but_not_sent = 1;
+               }
+               /*
+                * If flush_read_but_not_sent is true, we have already read one
+                * full request but have not fully sent it + EOF, which is why
+                * we need to refrain from reading.
+                */
+       }
+       if (rpc->flush_read_but_not_sent) {
+               if (!avail) {
+                       /*
+                        * The line length either does not need to be sent at
+                        * all or has already been completely sent. Now we can
+                        * return 0, indicating EOF, meaning that the flush has
+                        * been fully sent.
+                        */
+                       rpc->flush_read_but_not_sent = 0;
+                       return 0;
+               }
+               /*
+                * If avail is non-zerp, the line length for the flush still
+                * hasn't been fully sent. Proceed with sending the line
+                * length.
+                */
        }
 
        if (max < avail)
@@ -537,7 +670,7 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
                        rpc->pos = 0;
                        return CURLIOE_OK;
                }
-               error("unable to rewind rpc post data - try increasing http.postBuffer");
+               error(_("unable to rewind rpc post data - try increasing http.postBuffer"));
                return CURLIOE_FAILRESTART;
 
        default:
@@ -546,14 +679,30 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
 }
 #endif
 
+struct rpc_in_data {
+       struct rpc_state *rpc;
+       struct active_request_slot *slot;
+};
+
+/*
+ * A callback for CURLOPT_WRITEFUNCTION. The return value is the bytes consumed
+ * from ptr.
+ */
 static size_t rpc_in(char *ptr, size_t eltsize,
                size_t nmemb, void *buffer_)
 {
        size_t size = eltsize * nmemb;
-       struct rpc_state *rpc = buffer_;
+       struct rpc_in_data *data = buffer_;
+       long response_code;
+
+       if (curl_easy_getinfo(data->slot->curl, CURLINFO_RESPONSE_CODE,
+                             &response_code) != CURLE_OK)
+               return size;
+       if (response_code >= 300)
+               return size;
        if (size)
-               rpc->any_written = 1;
-       write_or_die(rpc->in, ptr, size);
+               data->rpc->any_written = 1;
+       write_or_die(data->rpc->in, ptr, size);
        return size;
 }
 
@@ -581,7 +730,7 @@ static int run_slot(struct active_request_slot *slot,
                                strbuf_addstr(&msg, curl_errorstr);
                        }
                }
-               error("RPC failed; %s", msg.buf);
+               error(_("RPC failed; %s"), msg.buf);
                strbuf_release(&msg);
        }
 
@@ -617,14 +766,19 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
        return err;
 }
 
-static curl_off_t xcurl_off_t(size_t len) {
+static curl_off_t xcurl_off_t(size_t len)
+{
        uintmax_t size = len;
        if (size > maximum_signed_value_of_type(curl_off_t))
-               die("cannot handle pushes this big");
+               die(_("cannot handle pushes this big"));
        return (curl_off_t)size;
 }
 
-static int post_rpc(struct rpc_state *rpc)
+/*
+ * If flush_received is true, do not attempt to read any more; just use what's
+ * in rpc->buf.
+ */
+static int post_rpc(struct rpc_state *rpc, int flush_received)
 {
        struct active_request_slot *slot;
        struct curl_slist *headers = http_copy_default_headers();
@@ -633,26 +787,25 @@ static int post_rpc(struct rpc_state *rpc)
        size_t gzip_size = 0;
        int err, large_request = 0;
        int needs_100_continue = 0;
+       struct rpc_in_data rpc_in_data;
 
        /* 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
         * chunked encoding mess.
         */
-       while (1) {
-               size_t left = rpc->alloc - rpc->len;
-               char *buf = rpc->buf + rpc->len;
-               int n;
-
-               if (left < LARGE_PACKET_MAX) {
-                       large_request = 1;
-                       use_gzip = 0;
-                       break;
+       if (!flush_received) {
+               while (1) {
+                       size_t n;
+                       enum packet_read_status status;
+
+                       if (!rpc_read_from_out(rpc, 0, &n, &status)) {
+                               large_request = 1;
+                               use_gzip = 0;
+                               break;
+                       }
+                       if (status == PACKET_READ_FLUSH)
+                               break;
                }
-
-               n = packet_read(rpc->out, NULL, NULL, buf, left, 0);
-               if (!n)
-                       break;
-               rpc->len += n;
        }
 
        if (large_request) {
@@ -732,11 +885,11 @@ retry:
 
                ret = git_deflate(&stream, Z_FINISH);
                if (ret != Z_STREAM_END)
-                       die("cannot deflate request; zlib deflate error %d", ret);
+                       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);
+                       die(_("cannot deflate request; zlib end error %d"), ret);
 
                gzip_size = stream.total_out;
 
@@ -765,7 +918,10 @@ retry:
 
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
-       curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
+       rpc_in_data.rpc = rpc;
+       rpc_in_data.slot = slot;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &rpc_in_data);
+       curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
 
 
        rpc->any_written = 0;
@@ -785,22 +941,22 @@ retry:
        return err;
 }
 
-static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
+static int rpc_service(struct rpc_state *rpc, struct discovery *heads,
+                      const char **client_argv, const struct strbuf *preamble,
+                      struct strbuf *rpc_result)
 {
        const char *svc = rpc->service_name;
        struct strbuf buf = STRBUF_INIT;
-       struct strbuf *preamble = rpc->stdin_preamble;
        struct child_process client = CHILD_PROCESS_INIT;
        int err = 0;
 
        client.in = -1;
        client.out = -1;
        client.git_cmd = 1;
-       client.argv = rpc->argv;
+       client.argv = client_argv;
        if (start_command(&client))
                exit(1);
-       if (preamble)
-               write_or_die(client.in, preamble->buf, preamble->len);
+       write_or_die(client.in, preamble->buf, preamble->len);
        if (heads)
                write_or_die(client.in, heads->buf, heads->len);
 
@@ -808,7 +964,6 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
        rpc->buf = xmalloc(rpc->alloc);
        rpc->in = client.in;
        rpc->out = client.out;
-       strbuf_init(&rpc->result, 0);
 
        strbuf_addf(&buf, "%s%s", url.buf, svc);
        rpc->service_url = strbuf_detach(&buf, NULL);
@@ -830,13 +985,13 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
                        break;
                rpc->pos = 0;
                rpc->len = n;
-               err |= post_rpc(rpc);
+               err |= post_rpc(rpc, 0);
        }
 
        close(client.in);
        client.in = -1;
        if (!err) {
-               strbuf_read(&rpc->result, client.out, 0);
+               strbuf_read(rpc_result, client.out, 0);
        } else {
                char buf[4096];
                for (;;)
@@ -865,7 +1020,7 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch)
 
        ALLOC_ARRAY(targets, nr_heads);
        if (options.depth || options.deepen_since)
-               die("dumb http transport does not support shallow capabilities");
+               die(_("dumb http transport does not support shallow capabilities"));
        for (i = 0; i < nr_heads; i++)
                targets[i] = xstrdup(oid_to_hex(&to_fetch[i]->old_oid));
 
@@ -879,7 +1034,7 @@ 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,
@@ -889,6 +1044,7 @@ static int fetch_git(struct discovery *heads,
        struct strbuf preamble = STRBUF_INIT;
        int i, err;
        struct argv_array args = ARGV_ARRAY_INIT;
+       struct strbuf rpc_result = STRBUF_INIT;
 
        argv_array_pushl(&args, "fetch-pack", "--stateless-rpc",
                         "--stdin", "--lock-pack", NULL);
@@ -926,7 +1082,7 @@ static int fetch_git(struct discovery *heads,
        for (i = 0; i < nr_heads; i++) {
                struct ref *ref = to_fetch[i];
                if (!*ref->name)
-                       die("cannot fetch by sha1 over smart http");
+                       die(_("cannot fetch by sha1 over smart http"));
                packet_buf_write(&preamble, "%s %s\n",
                                 oid_to_hex(&ref->old_oid), ref->name);
        }
@@ -934,14 +1090,12 @@ static int fetch_git(struct discovery *heads,
 
        memset(&rpc, 0, sizeof(rpc));
        rpc.service_name = "git-upload-pack",
-       rpc.argv = args.argv;
-       rpc.stdin_preamble = &preamble;
        rpc.gzip_request = 1;
 
-       err = rpc_service(&rpc, heads);
-       if (rpc.result.len)
-               write_or_die(1, rpc.result.buf, rpc.result.len);
-       strbuf_release(&rpc.result);
+       err = rpc_service(&rpc, heads, args.argv, &preamble, &rpc_result);
+       if (rpc_result.len)
+               write_or_die(1, rpc_result.buf, rpc_result.len);
+       strbuf_release(&rpc_result);
        strbuf_release(&preamble);
        argv_array_clear(&args);
        return err;
@@ -969,15 +1123,16 @@ static void parse_fetch(struct strbuf *buf)
                        const char *name;
                        struct ref *ref;
                        struct object_id old_oid;
+                       const char *q;
 
-                       if (get_oid_hex(p, &old_oid))
-                               die("protocol error: expected sha/ref, got %s'", p);
-                       if (p[GIT_SHA1_HEXSZ] == ' ')
-                               name = p + GIT_SHA1_HEXSZ + 1;
-                       else if (!p[GIT_SHA1_HEXSZ])
+                       if (parse_oid_hex(p, &old_oid, &q))
+                               die(_("protocol error: expected sha/ref, got '%s'"), p);
+                       if (*q == ' ')
+                               name = q + 1;
+                       else if (!*q)
                                name = "";
                        else
-                               die("protocol error: expected sha/ref, got %s'", p);
+                               die(_("protocol error: expected sha/ref, got '%s'"), p);
 
                        ref = alloc_ref(name);
                        oidcpy(&ref->old_oid, &old_oid);
@@ -989,7 +1144,7 @@ static void parse_fetch(struct strbuf *buf)
                        to_fetch[nr_heads++] = ref;
                }
                else
-                       die("http transport does not support %s", buf->buf);
+                       die(_("http transport does not support %s"), buf->buf);
 
                strbuf_reset(buf);
                if (strbuf_getline_lf(buf, stdin) == EOF)
@@ -1008,7 +1163,7 @@ static void parse_fetch(struct strbuf *buf)
        strbuf_reset(buf);
 }
 
-static int push_dav(int nr_spec, char **specs)
+static int push_dav(int nr_spec, const char **specs)
 {
        struct child_process child = CHILD_PROCESS_INIT;
        size_t i;
@@ -1025,17 +1180,18 @@ static int push_dav(int nr_spec, char **specs)
                argv_array_push(&child.args, specs[i]);
 
        if (run_command(&child))
-               die("git-http-push failed");
+               die(_("git-http-push failed"));
        return 0;
 }
 
-static int push_git(struct discovery *heads, int nr_spec, char **specs)
+static int push_git(struct discovery *heads, int nr_spec, const char **specs)
 {
        struct rpc_state rpc;
        int i, err;
        struct argv_array args;
        struct string_list_item *cas_option;
        struct strbuf preamble = STRBUF_INIT;
+       struct strbuf rpc_result = STRBUF_INIT;
 
        argv_array_init(&args);
        argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status",
@@ -1049,6 +1205,8 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
                argv_array_push(&args, "--signed=yes");
        else if (options.push_cert == SEND_PACK_PUSH_CERT_IF_ASKED)
                argv_array_push(&args, "--signed=if-asked");
+       if (options.atomic)
+               argv_array_push(&args, "--atomic");
        if (options.verbosity == 0)
                argv_array_push(&args, "--quiet");
        else if (options.verbosity > 1)
@@ -1068,19 +1226,17 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
 
        memset(&rpc, 0, sizeof(rpc));
        rpc.service_name = "git-receive-pack",
-       rpc.argv = args.argv;
-       rpc.stdin_preamble = &preamble;
 
-       err = rpc_service(&rpc, heads);
-       if (rpc.result.len)
-               write_or_die(1, rpc.result.buf, rpc.result.len);
-       strbuf_release(&rpc.result);
+       err = rpc_service(&rpc, heads, args.argv, &preamble, &rpc_result);
+       if (rpc_result.len)
+               write_or_die(1, rpc_result.buf, rpc_result.len);
+       strbuf_release(&rpc_result);
        strbuf_release(&preamble);
        argv_array_clear(&args);
        return err;
 }
 
-static int push(int nr_spec, char **specs)
+static int push(int nr_spec, const char **specs)
 {
        struct discovery *heads = discover_refs("git-receive-pack", 1);
        int ret;
@@ -1095,16 +1251,14 @@ 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, ret;
+       struct argv_array specs = ARGV_ARRAY_INIT;
+       int ret;
 
        do {
-               if (starts_with(buf->buf, "push ")) {
-                       ALLOC_GROW(specs, nr_spec + 1, alloc_spec);
-                       specs[nr_spec++] = xstrdup(buf->buf + 5);
-               }
+               if (starts_with(buf->buf, "push "))
+                       argv_array_push(&specs, buf->buf + 5);
                else
-                       die("http transport does not support %s", buf->buf);
+                       die(_("http transport does not support %s"), buf->buf);
 
                strbuf_reset(buf);
                if (strbuf_getline_lf(buf, stdin) == EOF)
@@ -1113,7 +1267,7 @@ static void parse_push(struct strbuf *buf)
                        break;
        } while (1);
 
-       ret = push(nr_spec, specs);
+       ret = push(specs.argc, specs.argv);
        printf("\n");
        fflush(stdout);
 
@@ -1121,169 +1275,14 @@ static void parse_push(struct strbuf *buf)
                exit(128); /* error already reported */
 
  free_specs:
-       for (i = 0; i < nr_spec; i++)
-               free(specs[i]);
-       free(specs);
-}
-
-/*
- * Used to represent the state of a connection to an HTTP server when
- * communicating using git's wire-protocol version 2.
- */
-struct proxy_state {
-       char *service_name;
-       char *service_url;
-       struct curl_slist *headers;
-       struct strbuf request_buffer;
-       int in;
-       int out;
-       struct packet_reader reader;
-       size_t pos;
-       int seen_flush;
-};
-
-static void proxy_state_init(struct proxy_state *p, const char *service_name,
-                            enum protocol_version version)
-{
-       struct strbuf buf = STRBUF_INIT;
-
-       memset(p, 0, sizeof(*p));
-       p->service_name = xstrdup(service_name);
-
-       p->in = 0;
-       p->out = 1;
-       strbuf_init(&p->request_buffer, 0);
-
-       strbuf_addf(&buf, "%s%s", url.buf, p->service_name);
-       p->service_url = strbuf_detach(&buf, NULL);
-
-       p->headers = http_copy_default_headers();
-
-       strbuf_addf(&buf, "Content-Type: application/x-%s-request", p->service_name);
-       p->headers = curl_slist_append(p->headers, buf.buf);
-       strbuf_reset(&buf);
-
-       strbuf_addf(&buf, "Accept: application/x-%s-result", p->service_name);
-       p->headers = curl_slist_append(p->headers, buf.buf);
-       strbuf_reset(&buf);
-
-       p->headers = curl_slist_append(p->headers, "Transfer-Encoding: chunked");
-
-       /* Add the Git-Protocol header */
-       if (get_protocol_http_header(version, &buf))
-               p->headers = curl_slist_append(p->headers, buf.buf);
-
-       packet_reader_init(&p->reader, p->in, NULL, 0,
-                          PACKET_READ_GENTLE_ON_EOF);
-
-       strbuf_release(&buf);
-}
-
-static void proxy_state_clear(struct proxy_state *p)
-{
-       free(p->service_name);
-       free(p->service_url);
-       curl_slist_free_all(p->headers);
-       strbuf_release(&p->request_buffer);
-}
-
-/*
- * CURLOPT_READFUNCTION callback function.
- * Attempts to copy over a single packet-line at a time into the
- * curl provided buffer.
- */
-static size_t proxy_in(char *buffer, size_t eltsize,
-                      size_t nmemb, void *userdata)
-{
-       size_t max;
-       struct proxy_state *p = userdata;
-       size_t avail = p->request_buffer.len - p->pos;
-
-
-       if (eltsize != 1)
-               BUG("curl read callback called with size = %"PRIuMAX" != 1",
-                   (uintmax_t)eltsize);
-       max = nmemb;
-
-       if (!avail) {
-               if (p->seen_flush) {
-                       p->seen_flush = 0;
-                       return 0;
-               }
-
-               strbuf_reset(&p->request_buffer);
-               switch (packet_reader_read(&p->reader)) {
-               case PACKET_READ_EOF:
-                       die("unexpected EOF when reading from parent process");
-               case PACKET_READ_NORMAL:
-                       packet_buf_write_len(&p->request_buffer, p->reader.line,
-                                            p->reader.pktlen);
-                       break;
-               case PACKET_READ_DELIM:
-                       packet_buf_delim(&p->request_buffer);
-                       break;
-               case PACKET_READ_FLUSH:
-                       packet_buf_flush(&p->request_buffer);
-                       p->seen_flush = 1;
-                       break;
-               }
-               p->pos = 0;
-               avail = p->request_buffer.len;
-       }
-
-       if (max < avail)
-               avail = max;
-       memcpy(buffer, p->request_buffer.buf + p->pos, avail);
-       p->pos += avail;
-       return avail;
-}
-
-static size_t proxy_out(char *buffer, size_t eltsize,
-                       size_t nmemb, void *userdata)
-{
-       size_t size;
-       struct proxy_state *p = userdata;
-
-       if (eltsize != 1)
-               BUG("curl read callback called with size = %"PRIuMAX" != 1",
-                   (uintmax_t)eltsize);
-       size = nmemb;
-
-       write_or_die(p->out, buffer, size);
-       return size;
-}
-
-/* Issues a request to the HTTP server configured in `p` */
-static int proxy_request(struct proxy_state *p)
-{
-       struct active_request_slot *slot;
-
-       slot = get_active_slot();
-
-       curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
-       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
-       curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
-       curl_easy_setopt(slot->curl, CURLOPT_URL, p->service_url);
-       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, p->headers);
-
-       /* Setup function to read request from client */
-       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, proxy_in);
-       curl_easy_setopt(slot->curl, CURLOPT_READDATA, p);
-
-       /* Setup function to write server response to client */
-       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, proxy_out);
-       curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, p);
-
-       if (run_slot(slot, NULL) != HTTP_OK)
-               return -1;
-
-       return 0;
+       argv_array_clear(&specs);
 }
 
 static int stateless_connect(const char *service_name)
 {
        struct discovery *discover;
-       struct proxy_state p;
+       struct rpc_state rpc;
+       struct strbuf buf = STRBUF_INIT;
 
        /*
         * Run the info/refs request and see if the server supports protocol
@@ -1303,23 +1302,58 @@ static int stateless_connect(const char *service_name)
                fflush(stdout);
        }
 
-       proxy_state_init(&p, service_name, discover->version);
+       rpc.service_name = service_name;
+       rpc.service_url = xstrfmt("%s%s", url.buf, rpc.service_name);
+       rpc.hdr_content_type = xstrfmt("Content-Type: application/x-%s-request", rpc.service_name);
+       rpc.hdr_accept = xstrfmt("Accept: application/x-%s-result", rpc.service_name);
+       if (get_protocol_http_header(discover->version, &buf)) {
+               rpc.protocol_header = strbuf_detach(&buf, NULL);
+       } else {
+               rpc.protocol_header = NULL;
+               strbuf_release(&buf);
+       }
+       rpc.buf = xmalloc(http_post_buffer);
+       rpc.alloc = http_post_buffer;
+       rpc.len = 0;
+       rpc.pos = 0;
+       rpc.in = 1;
+       rpc.out = 0;
+       rpc.any_written = 0;
+       rpc.gzip_request = 1;
+       rpc.initial_buffer = 0;
+       rpc.write_line_lengths = 1;
+       rpc.flush_read_but_not_sent = 0;
 
        /*
         * Dump the capability listing that we got from the server earlier
         * during the info/refs request.
         */
-       write_or_die(p.out, discover->buf, discover->len);
+       write_or_die(rpc.in, discover->buf, discover->len);
+
+       /* Until we see EOF keep sending POSTs */
+       while (1) {
+               size_t avail;
+               enum packet_read_status status;
 
-       /* Peek the next packet line.  Until we see EOF keep sending POSTs */
-       while (packet_reader_peek(&p.reader) != PACKET_READ_EOF) {
-               if (proxy_request(&p)) {
+               if (!rpc_read_from_out(&rpc, PACKET_READ_GENTLE_ON_EOF, &avail,
+                                      &status))
+                       BUG("The entire rpc->buf should be larger than LARGE_PACKET_MAX");
+               if (status == PACKET_READ_EOF)
+                       break;
+               if (post_rpc(&rpc, status == PACKET_READ_FLUSH))
                        /* We would have an err here */
                        break;
-               }
+               /* Reset the buffer for next request */
+               rpc.len = 0;
        }
 
-       proxy_state_clear(&p);
+       free(rpc.service_url);
+       free(rpc.hdr_content_type);
+       free(rpc.hdr_accept);
+       free(rpc.protocol_header);
+       free(rpc.buf);
+       strbuf_release(&buf);
+
        return 0;
 }
 
@@ -1330,7 +1364,7 @@ int cmd_main(int argc, const char **argv)
 
        setup_git_directory_gently(&nongit);
        if (argc < 2) {
-               error("remote-curl: usage: git remote-curl <remote> [<url>]");
+               error(_("remote-curl: usage: git remote-curl <remote> [<url>]"));
                return 1;
        }
 
@@ -1340,6 +1374,13 @@ int cmd_main(int argc, const char **argv)
        string_list_init(&options.deepen_not, 1);
        string_list_init(&options.push_options, 1);
 
+       /*
+        * Just report "remote-curl" here (folding all the various aliases
+        * ("git-remote-http", "git-remote-https", and etc.) here since they
+        * are all just copies of the same actual executable.
+        */
+       trace2_cmd_name("remote-curl");
+
        remote = remote_get(argv[1]);
 
        if (argc > 2) {
@@ -1355,14 +1396,14 @@ int cmd_main(int argc, const char **argv)
 
                if (strbuf_getline_lf(&buf, stdin) == EOF) {
                        if (ferror(stdin))
-                               error("remote-curl: error reading command stream from git");
+                               error(_("remote-curl: error reading command stream from git"));
                        return 1;
                }
                if (buf.len == 0)
                        break;
                if (starts_with(buf.buf, "fetch ")) {
                        if (nongit)
-                               die("remote-curl: fetch attempted without a local repo");
+                               die(_("remote-curl: fetch attempted without a local repo"));
                        parse_fetch(&buf);
 
                } else if (!strcmp(buf.buf, "list") || starts_with(buf.buf, "list ")) {
@@ -1402,7 +1443,7 @@ int cmd_main(int argc, const char **argv)
                        if (!stateless_connect(arg))
                                break;
                } else {
-                       error("remote-curl: unknown command '%s' from git", buf.buf);
+                       error(_("remote-curl: unknown command '%s' from git"), buf.buf);
                        return 1;
                }
                strbuf_reset(&buf);