Imported Upstream version 2.16.6
[platform/upstream/git.git] / transport-helper.c
index f6b3b1f..de2cd44 100644 (file)
@@ -5,11 +5,13 @@
 #include "commit.h"
 #include "diff.h"
 #include "revision.h"
-#include "quote.h"
 #include "remote.h"
 #include "string-list.h"
 #include "thread-utils.h"
 #include "sigchain.h"
+#include "argv-array.h"
+#include "refs.h"
+#include "transport-internal.h"
 
 static int debug;
 
@@ -19,11 +21,15 @@ struct helper_data {
        FILE *out;
        unsigned fetch : 1,
                import : 1,
+               bidi_import : 1,
                export : 1,
                option : 1,
                push : 1,
                connect : 1,
-               no_disconnect_req : 1;
+               signed_tags : 1,
+               check_connectivity : 1,
+               no_disconnect_req : 1,
+               no_private_update : 1;
        char *export_marks;
        char *import_marks;
        /* These go from remote name (as in "list") to private name */
@@ -39,20 +45,19 @@ static void sendline(struct helper_data *helper, struct strbuf *buffer)
 {
        if (debug)
                fprintf(stderr, "Debug: Remote helper: -> %s", buffer->buf);
-       if (write_in_full(helper->helper->in, buffer->buf, buffer->len)
-               != buffer->len)
+       if (write_in_full(helper->helper->in, buffer->buf, buffer->len) < 0)
                die_errno("Full write to remote helper failed");
 }
 
-static int recvline_fh(FILE *helper, struct strbuf *buffer)
+static int recvline_fh(FILE *helper, struct strbuf *buffer, const char *name)
 {
        strbuf_reset(buffer);
        if (debug)
                fprintf(stderr, "Debug: Remote helper: Waiting...\n");
-       if (strbuf_getline(buffer, helper, '\n') == EOF) {
+       if (strbuf_getline(buffer, helper) == EOF) {
                if (debug)
                        fprintf(stderr, "Debug: Remote helper quit.\n");
-               exit(128);
+               return 1;
        }
 
        if (debug)
@@ -62,20 +67,14 @@ static int recvline_fh(FILE *helper, struct strbuf *buffer)
 
 static int recvline(struct helper_data *helper, struct strbuf *buffer)
 {
-       return recvline_fh(helper->out, buffer);
-}
-
-static void xchgline(struct helper_data *helper, struct strbuf *buffer)
-{
-       sendline(helper, buffer);
-       recvline(helper, buffer);
+       return recvline_fh(helper->out, buffer, helper->name);
 }
 
 static void write_constant(int fd, const char *str)
 {
        if (debug)
                fprintf(stderr, "Debug: Remote helper: -> %s", str);
-       if (write_in_full(fd, str, strlen(str)) != strlen(str))
+       if (write_in_full(fd, str, strlen(str)) < 0)
                die_errno("Full write to remote helper failed");
 }
 
@@ -98,6 +97,8 @@ static void do_take_over(struct transport *transport)
        free(data);
 }
 
+static void standard_options(struct transport *t);
+
 static struct child_process *get_helper(struct transport *transport)
 {
        struct helper_data *data = transport->data;
@@ -108,30 +109,24 @@ static struct child_process *get_helper(struct transport *transport)
        int refspec_alloc = 0;
        int duped;
        int code;
-       char git_dir_buf[sizeof(GIT_DIR_ENVIRONMENT) + PATH_MAX + 1];
-       const char *helper_env[] = {
-               git_dir_buf,
-               NULL
-       };
-
 
        if (data->helper)
                return data->helper;
 
-       helper = xcalloc(1, sizeof(*helper));
+       helper = xmalloc(sizeof(*helper));
+       child_process_init(helper);
        helper->in = -1;
        helper->out = -1;
        helper->err = 0;
-       helper->argv = xcalloc(4, sizeof(*helper->argv));
-       strbuf_addf(&buf, "git-remote-%s", data->name);
-       helper->argv[0] = strbuf_detach(&buf, NULL);
-       helper->argv[1] = transport->remote->name;
-       helper->argv[2] = remove_ext_force(transport->url);
+       argv_array_pushf(&helper->args, "git-remote-%s", data->name);
+       argv_array_push(&helper->args, transport->remote->name);
+       argv_array_push(&helper->args, remove_ext_force(transport->url));
        helper->git_cmd = 0;
        helper->silent_exec_failure = 1;
 
-       snprintf(git_dir_buf, sizeof(git_dir_buf), "%s=%s", GIT_DIR_ENVIRONMENT, get_git_dir());
-       helper->env = helper_env;
+       if (have_git_dir())
+               argv_array_pushf(&helper->env_array, "%s=%s",
+                                GIT_DIR_ENVIRONMENT, get_git_dir());
 
        code = start_command(helper);
        if (code < 0 && errno == ENOENT)
@@ -143,7 +138,8 @@ static struct child_process *get_helper(struct transport *transport)
        data->no_disconnect_req = 0;
 
        /*
-        * Open the output as FILE* so strbuf_getline() can be used.
+        * Open the output as FILE* so strbuf_getline_*() family of
+        * functions can be used.
         * Do this with duped fd because fclose() will close the fd,
         * and stuff like taking over will require the fd to remain.
         */
@@ -155,9 +151,10 @@ static struct child_process *get_helper(struct transport *transport)
        write_constant(helper->in, "capabilities\n");
 
        while (1) {
-               const char *capname;
+               const char *capname, *arg;
                int mandatory = 0;
-               recvline(data, &buf);
+               if (recvline(data, &buf))
+                       exit(128);
 
                if (!*buf.buf)
                        break;
@@ -178,28 +175,30 @@ static struct child_process *get_helper(struct transport *transport)
                        data->push = 1;
                else if (!strcmp(capname, "import"))
                        data->import = 1;
+               else if (!strcmp(capname, "bidi-import"))
+                       data->bidi_import = 1;
                else if (!strcmp(capname, "export"))
                        data->export = 1;
-               else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
+               else if (!strcmp(capname, "check-connectivity"))
+                       data->check_connectivity = 1;
+               else if (!data->refspecs && skip_prefix(capname, "refspec ", &arg)) {
                        ALLOC_GROW(refspecs,
                                   refspec_nr + 1,
                                   refspec_alloc);
-                       refspecs[refspec_nr++] = xstrdup(capname + strlen("refspec "));
+                       refspecs[refspec_nr++] = xstrdup(arg);
                } else if (!strcmp(capname, "connect")) {
                        data->connect = 1;
-               } else if (!prefixcmp(capname, "export-marks ")) {
-                       struct strbuf arg = STRBUF_INIT;
-                       strbuf_addstr(&arg, "--export-marks=");
-                       strbuf_addstr(&arg, capname + strlen("export-marks "));
-                       data->export_marks = strbuf_detach(&arg, NULL);
-               } else if (!prefixcmp(capname, "import-marks")) {
-                       struct strbuf arg = STRBUF_INIT;
-                       strbuf_addstr(&arg, "--import-marks=");
-                       strbuf_addstr(&arg, capname + strlen("import-marks "));
-                       data->import_marks = strbuf_detach(&arg, NULL);
+               } else if (!strcmp(capname, "signed-tags")) {
+                       data->signed_tags = 1;
+               } else if (skip_prefix(capname, "export-marks ", &arg)) {
+                       data->export_marks = xstrdup(arg);
+               } else if (skip_prefix(capname, "import-marks ", &arg)) {
+                       data->import_marks = xstrdup(arg);
+               } else if (starts_with(capname, "no-private-update")) {
+                       data->no_private_update = 1;
                } else if (mandatory) {
                        die("Unknown mandatory capability %s. This remote "
-                           "helper probably needs newer version of Git.\n",
+                           "helper probably needs newer version of Git.",
                            capname);
                }
        }
@@ -207,14 +206,16 @@ static struct child_process *get_helper(struct transport *transport)
                int i;
                data->refspec_nr = refspec_nr;
                data->refspecs = parse_fetch_refspec(refspec_nr, refspecs);
-               for (i = 0; i < refspec_nr; i++) {
+               for (i = 0; i < refspec_nr; i++)
                        free((char *)refspecs[i]);
-               }
                free(refspecs);
+       } else if (data->import || data->bidi_import || data->export) {
+               warning("This remote helper should implement refspec capability.");
        }
        strbuf_release(&buf);
        if (debug)
                fprintf(stderr, "Debug: Capabilities complete.\n");
+       standard_options(transport);
        return data->helper;
 }
 
@@ -241,10 +242,7 @@ static int disconnect_helper(struct transport *transport)
                close(data->helper->out);
                fclose(data->out);
                res = finish_command(data->helper);
-               free((char *)data->helper->argv[0]);
-               free(data->helper->argv);
-               free(data->helper);
-               data->helper = NULL;
+               FREE_AND_NULL(data->helper);
        }
        return res;
 }
@@ -255,12 +253,56 @@ static const char *unsupported_options[] = {
        TRANS_OPT_THIN,
        TRANS_OPT_KEEP
        };
+
 static const char *boolean_options[] = {
        TRANS_OPT_THIN,
        TRANS_OPT_KEEP,
-       TRANS_OPT_FOLLOWTAGS
+       TRANS_OPT_FOLLOWTAGS,
+       TRANS_OPT_DEEPEN_RELATIVE
        };
 
+static int strbuf_set_helper_option(struct helper_data *data,
+                                   struct strbuf *buf)
+{
+       int ret;
+
+       sendline(data, buf);
+       if (recvline(data, buf))
+               exit(128);
+
+       if (!strcmp(buf->buf, "ok"))
+               ret = 0;
+       else if (starts_with(buf->buf, "error"))
+               ret = -1;
+       else if (!strcmp(buf->buf, "unsupported"))
+               ret = 1;
+       else {
+               warning("%s unexpectedly said: '%s'", data->name, buf->buf);
+               ret = 1;
+       }
+       return ret;
+}
+
+static int string_list_set_helper_option(struct helper_data *data,
+                                        const char *name,
+                                        struct string_list *list)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int i, ret = 0;
+
+       for (i = 0; i < list->nr; i++) {
+               strbuf_addf(&buf, "option %s ", name);
+               quote_c_style(list->items[i].string, &buf, NULL, 0);
+               strbuf_addch(&buf, '\n');
+
+               if ((ret = strbuf_set_helper_option(data, &buf)))
+                       break;
+               strbuf_reset(&buf);
+       }
+       strbuf_release(&buf);
+       return ret;
+}
+
 static int set_helper_option(struct transport *transport,
                          const char *name, const char *value)
 {
@@ -273,6 +315,10 @@ static int set_helper_option(struct transport *transport,
        if (!data->option)
                return 1;
 
+       if (!strcmp(name, "deepen-not"))
+               return string_list_set_helper_option(data, name,
+                                                    (struct string_list *)value);
+
        for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) {
                if (!strcmp(name, unsupported_options[i]))
                        return 1;
@@ -292,18 +338,7 @@ static int set_helper_option(struct transport *transport,
                quote_c_style(value, &buf, NULL, 0);
        strbuf_addch(&buf, '\n');
 
-       xchgline(data, &buf);
-
-       if (!strcmp(buf.buf, "ok"))
-               ret = 0;
-       else if (!prefixcmp(buf.buf, "error")) {
-               ret = -1;
-       } else if (!strcmp(buf.buf, "unsupported"))
-               ret = 1;
-       else {
-               warning("%s unexpectedly said: '%s'", data->name, buf.buf);
-               ret = 1;
-       }
+       ret = strbuf_set_helper_option(data, &buf);
        strbuf_release(&buf);
        return ret;
 }
@@ -311,15 +346,27 @@ static int set_helper_option(struct transport *transport,
 static void standard_options(struct transport *t)
 {
        char buf[16];
-       int n;
        int v = t->verbose;
 
        set_helper_option(t, "progress", t->progress ? "true" : "false");
 
-       n = snprintf(buf, sizeof(buf), "%d", v + 1);
-       if (n >= sizeof(buf))
-               die("impossibly large verbosity value");
+       xsnprintf(buf, sizeof(buf), "%d", v + 1);
        set_helper_option(t, "verbosity", buf);
+
+       switch (t->family) {
+       case TRANSPORT_FAMILY_ALL:
+               /*
+                * this is already the default,
+                * do not break old remote helpers by setting "all" here
+                */
+               break;
+       case TRANSPORT_FAMILY_IPV4:
+               set_helper_option(t, "family", "ipv4");
+               break;
+       case TRANSPORT_FAMILY_IPV6:
+               set_helper_option(t, "family", "ipv6");
+               break;
+       }
 }
 
 static int release_helper(struct transport *transport)
@@ -340,30 +387,34 @@ static int fetch_with_fetch(struct transport *transport,
        int i;
        struct strbuf buf = STRBUF_INIT;
 
-       standard_options(transport);
-
        for (i = 0; i < nr_heads; i++) {
                const struct ref *posn = to_fetch[i];
                if (posn->status & REF_STATUS_UPTODATE)
                        continue;
 
                strbuf_addf(&buf, "fetch %s %s\n",
-                           sha1_to_hex(posn->old_sha1), posn->name);
+                           oid_to_hex(&posn->old_oid),
+                           posn->symref ? posn->symref : posn->name);
        }
 
        strbuf_addch(&buf, '\n');
        sendline(data, &buf);
 
        while (1) {
-               recvline(data, &buf);
+               if (recvline(data, &buf))
+                       exit(128);
 
-               if (!prefixcmp(buf.buf, "lock ")) {
+               if (starts_with(buf.buf, "lock ")) {
                        const char *name = buf.buf + 5;
                        if (transport->pack_lockfile)
                                warning("%s also locked %s", data->name, name);
                        else
                                transport->pack_lockfile = xstrdup(name);
                }
+               else if (data->check_connectivity &&
+                        data->transport_options.check_self_contained_and_connected &&
+                        !strcmp(buf.buf, "connectivity-ok"))
+                       data->transport_options.self_contained_and_connected = 1;
                else if (!buf.len)
                        break;
                else
@@ -376,14 +427,22 @@ static int fetch_with_fetch(struct transport *transport,
 static int get_importer(struct transport *transport, struct child_process *fastimport)
 {
        struct child_process *helper = get_helper(transport);
-       memset(fastimport, 0, sizeof(*fastimport));
+       struct helper_data *data = transport->data;
+       int cat_blob_fd, code;
+       child_process_init(fastimport);
        fastimport->in = helper->out;
-       fastimport->argv = xcalloc(5, sizeof(*fastimport->argv));
-       fastimport->argv[0] = "fast-import";
-       fastimport->argv[1] = "--quiet";
+       argv_array_push(&fastimport->args, "fast-import");
+       argv_array_push(&fastimport->args, "--allow-unsafe-features");
+       argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet");
 
+       if (data->bidi_import) {
+               cat_blob_fd = xdup(helper->in);
+               argv_array_pushf(&fastimport->args, "--cat-blob-fd=%d", cat_blob_fd);
+       }
        fastimport->git_cmd = 1;
-       return start_command(fastimport);
+
+       code = start_command(fastimport);
+       return code;
 }
 
 static int get_exporter(struct transport *transport,
@@ -392,22 +451,24 @@ static int get_exporter(struct transport *transport,
 {
        struct helper_data *data = transport->data;
        struct child_process *helper = get_helper(transport);
-       int argc = 0, i;
-       memset(fastexport, 0, sizeof(*fastexport));
+       int i;
+
+       child_process_init(fastexport);
 
        /* we need to duplicate helper->in because we want to use it after
         * fastexport is done with it. */
        fastexport->out = dup(helper->in);
-       fastexport->argv = xcalloc(5 + revlist_args->nr, sizeof(*fastexport->argv));
-       fastexport->argv[argc++] = "fast-export";
-       fastexport->argv[argc++] = "--use-done-feature";
+       argv_array_push(&fastexport->args, "fast-export");
+       argv_array_push(&fastexport->args, "--use-done-feature");
+       argv_array_push(&fastexport->args, data->signed_tags ?
+               "--signed-tags=verbatim" : "--signed-tags=warn-strip");
        if (data->export_marks)
-               fastexport->argv[argc++] = data->export_marks;
+               argv_array_pushf(&fastexport->args, "--export-marks=%s.tmp", data->export_marks);
        if (data->import_marks)
-               fastexport->argv[argc++] = data->import_marks;
+               argv_array_pushf(&fastexport->args, "--import-marks=%s", data->import_marks);
 
        for (i = 0; i < revlist_args->nr; i++)
-               fastexport->argv[argc++] = revlist_args->items[i].string;
+               argv_array_push(&fastexport->args, revlist_args->items[i].string);
 
        fastexport->git_cmd = 1;
        return start_command(fastexport);
@@ -432,29 +493,52 @@ static int fetch_with_import(struct transport *transport,
                if (posn->status & REF_STATUS_UPTODATE)
                        continue;
 
-               strbuf_addf(&buf, "import %s\n", posn->name);
+               strbuf_addf(&buf, "import %s\n",
+                           posn->symref ? posn->symref : posn->name);
                sendline(data, &buf);
                strbuf_reset(&buf);
        }
 
        write_constant(data->helper->in, "\n");
+       /*
+        * remote-helpers that advertise the bidi-import capability are required to
+        * buffer the complete batch of import commands until this newline before
+        * sending data to fast-import.
+        * These helpers read back data from fast-import on their stdin, which could
+        * be mixed with import commands, otherwise.
+        */
 
        if (finish_command(&fastimport))
                die("Error while running fast-import");
-       free(fastimport.argv);
-       fastimport.argv = NULL;
 
+       /*
+        * The fast-import stream of a remote helper that advertises
+        * the "refspec" capability writes to the refs named after the
+        * right hand side of the first refspec matching each ref we
+        * were fetching.
+        *
+        * (If no "refspec" capability was specified, for historical
+        * reasons we default to the equivalent of *:*.)
+        *
+        * Store the result in to_fetch[i].old_sha1.  Callers such
+        * as "git fetch" can use the value to write feedback to the
+        * terminal, populate FETCH_HEAD, and determine what new value
+        * should be written to peer_ref if the update is a
+        * fast-forward or this is a forced update.
+        */
        for (i = 0; i < nr_heads; i++) {
-               char *private;
+               char *private, *name;
                posn = to_fetch[i];
                if (posn->status & REF_STATUS_UPTODATE)
                        continue;
+               name = posn->symref ? posn->symref : posn->name;
                if (data->refspecs)
-                       private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name);
+                       private = apply_refspecs(data->refspecs, data->refspec_nr, name);
                else
-                       private = xstrdup(posn->name);
+                       private = xstrdup(name);
                if (private) {
-                       read_ref(private, posn->old_sha1);
+                       if (read_ref(private, &posn->old_oid) < 0)
+                               die("Could not read ref %s", private);
                        free(private);
                }
        }
@@ -503,7 +587,9 @@ static int process_connect_service(struct transport *transport,
                goto exit;
 
        sendline(data, &cmdbuf);
-       recvline_fh(input, &cmdbuf);
+       if (recvline_fh(input, &cmdbuf, name))
+               exit(128);
+
        if (!strcmp(cmdbuf.buf, "")) {
                data->no_disconnect_req = 1;
                if (debug)
@@ -519,6 +605,7 @@ static int process_connect_service(struct transport *transport,
                        cmdbuf.buf);
 
 exit:
+       strbuf_release(&cmdbuf);
        fclose(input);
        return ret;
 }
@@ -565,7 +652,7 @@ static int fetch(struct transport *transport,
 
        if (process_connect(transport, 0)) {
                do_take_over(transport);
-               return transport->fetch(transport, nr_heads, to_fetch);
+               return transport->vtable->fetch(transport, nr_heads, to_fetch);
        }
 
        count = 0;
@@ -576,6 +663,16 @@ static int fetch(struct transport *transport,
        if (!count)
                return 0;
 
+       if (data->check_connectivity &&
+           data->transport_options.check_self_contained_and_connected)
+               set_helper_option(transport, "check-connectivity", "true");
+
+       if (transport->cloning)
+               set_helper_option(transport, "cloning", "true");
+
+       if (data->transport_options.update_shallow)
+               set_helper_option(transport, "update-shallow", "true");
+
        if (data->fetch)
                return fetch_with_fetch(transport, nr_heads, to_fetch);
 
@@ -585,21 +682,21 @@ static int fetch(struct transport *transport,
        return -1;
 }
 
-static void push_update_ref_status(struct strbuf *buf,
+static int push_update_ref_status(struct strbuf *buf,
                                   struct ref **ref,
                                   struct ref *remote_refs)
 {
        char *refname, *msg;
-       int status;
+       int status, forced = 0;
 
-       if (!prefixcmp(buf->buf, "ok ")) {
+       if (starts_with(buf->buf, "ok ")) {
                status = REF_STATUS_OK;
                refname = buf->buf + 3;
-       } else if (!prefixcmp(buf->buf, "error ")) {
+       } else if (starts_with(buf->buf, "error ")) {
                status = REF_STATUS_REMOTE_REJECT;
                refname = buf->buf + 6;
        } else
-               die("expected ok/error, helper said '%s'\n", buf->buf);
+               die("expected ok/error, helper said '%s'", buf->buf);
 
        msg = strchr(refname, ' ');
        if (msg) {
@@ -615,18 +712,35 @@ static void push_update_ref_status(struct strbuf *buf,
 
                if (!strcmp(msg, "no match")) {
                        status = REF_STATUS_NONE;
-                       free(msg);
-                       msg = NULL;
+                       FREE_AND_NULL(msg);
                }
                else if (!strcmp(msg, "up to date")) {
                        status = REF_STATUS_UPTODATE;
-                       free(msg);
-                       msg = NULL;
+                       FREE_AND_NULL(msg);
                }
                else if (!strcmp(msg, "non-fast forward")) {
                        status = REF_STATUS_REJECT_NONFASTFORWARD;
-                       free(msg);
-                       msg = NULL;
+                       FREE_AND_NULL(msg);
+               }
+               else if (!strcmp(msg, "already exists")) {
+                       status = REF_STATUS_REJECT_ALREADY_EXISTS;
+                       FREE_AND_NULL(msg);
+               }
+               else if (!strcmp(msg, "fetch first")) {
+                       status = REF_STATUS_REJECT_FETCH_FIRST;
+                       FREE_AND_NULL(msg);
+               }
+               else if (!strcmp(msg, "needs force")) {
+                       status = REF_STATUS_REJECT_NEEDS_FORCE;
+                       FREE_AND_NULL(msg);
+               }
+               else if (!strcmp(msg, "stale info")) {
+                       status = REF_STATUS_REJECT_STALE;
+                       FREE_AND_NULL(msg);
+               }
+               else if (!strcmp(msg, "forced update")) {
+                       forced = 1;
+                       FREE_AND_NULL(msg);
                }
        }
 
@@ -636,7 +750,7 @@ static void push_update_ref_status(struct strbuf *buf,
                *ref = find_ref_by_name(remote_refs, refname);
        if (!*ref) {
                warning("helper reported unexpected status of %s", refname);
-               return;
+               return 1;
        }
 
        if ((*ref)->status != REF_STATUS_NONE) {
@@ -645,36 +759,84 @@ static void push_update_ref_status(struct strbuf *buf,
                 * status reported by the remote helper if the latter is 'no match'.
                 */
                if (status == REF_STATUS_NONE)
-                       return;
+                       return 1;
        }
 
        (*ref)->status = status;
+       (*ref)->forced_update |= forced;
        (*ref)->remote_status = msg;
+       return !(status == REF_STATUS_OK);
 }
 
-static void push_update_refs_status(struct helper_data *data,
-                                   struct ref *remote_refs)
+static int push_update_refs_status(struct helper_data *data,
+                                   struct ref *remote_refs,
+                                   int flags)
 {
        struct strbuf buf = STRBUF_INIT;
        struct ref *ref = remote_refs;
+       int ret = 0;
+
        for (;;) {
-               recvline(data, &buf);
+               char *private;
+
+               if (recvline(data, &buf)) {
+                       ret = 1;
+                       break;
+               }
+
                if (!buf.len)
                        break;
 
-               push_update_ref_status(&buf, &ref, remote_refs);
+               if (push_update_ref_status(&buf, &ref, remote_refs))
+                       continue;
+
+               if (flags & TRANSPORT_PUSH_DRY_RUN || !data->refspecs || data->no_private_update)
+                       continue;
+
+               /* propagate back the update to the remote namespace */
+               private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
+               if (!private)
+                       continue;
+               update_ref("update by helper", private, &ref->new_oid, NULL,
+                          0, 0);
+               free(private);
        }
        strbuf_release(&buf);
+       return ret;
+}
+
+static void set_common_push_options(struct transport *transport,
+                                  const char *name, int flags)
+{
+       if (flags & TRANSPORT_PUSH_DRY_RUN) {
+               if (set_helper_option(transport, "dry-run", "true") != 0)
+                       die("helper %s does not support dry-run", name);
+       } else if (flags & TRANSPORT_PUSH_CERT_ALWAYS) {
+               if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
+                       die("helper %s does not support --signed", name);
+       } else if (flags & TRANSPORT_PUSH_CERT_IF_ASKED) {
+               if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "if-asked") != 0)
+                       die("helper %s does not support --signed=if-asked", name);
+       }
+
+       if (flags & TRANSPORT_PUSH_OPTIONS) {
+               struct string_list_item *item;
+               for_each_string_list_item(item, transport->push_options)
+                       if (set_helper_option(transport, "push-option", item->string) != 0)
+                               die("helper %s does not support 'push-option'", name);
+       }
 }
 
 static int push_refs_with_push(struct transport *transport,
-               struct ref *remote_refs, int flags)
+                              struct ref *remote_refs, int flags)
 {
        int force_all = flags & TRANSPORT_PUSH_FORCE;
        int mirror = flags & TRANSPORT_PUSH_MIRROR;
        struct helper_data *data = transport->data;
        struct strbuf buf = STRBUF_INIT;
        struct ref *ref;
+       struct string_list cas_options = STRING_LIST_INIT_DUP;
+       struct string_list_item *cas_option;
 
        get_helper(transport);
        if (!data->push)
@@ -687,6 +849,8 @@ static int push_refs_with_push(struct transport *transport,
                /* Check for statuses set by set_ref_status_for_push() */
                switch (ref->status) {
                case REF_STATUS_REJECT_NONFASTFORWARD:
+               case REF_STATUS_REJECT_STALE:
+               case REF_STATUS_REJECT_ALREADY_EXISTS:
                case REF_STATUS_UPTODATE:
                        continue;
                default:
@@ -703,28 +867,42 @@ static int push_refs_with_push(struct transport *transport,
                        if (ref->peer_ref)
                                strbuf_addstr(&buf, ref->peer_ref->name);
                        else
-                               strbuf_addstr(&buf, sha1_to_hex(ref->new_sha1));
+                               strbuf_addstr(&buf, oid_to_hex(&ref->new_oid));
                }
                strbuf_addch(&buf, ':');
                strbuf_addstr(&buf, ref->name);
                strbuf_addch(&buf, '\n');
+
+               /*
+                * The "--force-with-lease" options without explicit
+                * values to expect have already been expanded into
+                * the ref->old_oid_expect[] field; we can ignore
+                * transport->smart_options->cas altogether and instead
+                * can enumerate them from the refs.
+                */
+               if (ref->expect_old_sha1) {
+                       struct strbuf cas = STRBUF_INIT;
+                       strbuf_addf(&cas, "%s:%s",
+                                   ref->name, oid_to_hex(&ref->old_oid_expect));
+                       string_list_append_nodup(&cas_options,
+                                                strbuf_detach(&cas, NULL));
+               }
        }
-       if (buf.len == 0)
+       if (buf.len == 0) {
+               string_list_clear(&cas_options, 0);
                return 0;
-
-       standard_options(transport);
-
-       if (flags & TRANSPORT_PUSH_DRY_RUN) {
-               if (set_helper_option(transport, "dry-run", "true") != 0)
-                       die("helper %s does not support dry-run", data->name);
        }
 
+       for_each_string_list_item(cas_option, &cas_options)
+               set_helper_option(transport, "cas", cas_option->string);
+       set_common_push_options(transport, data->name, flags);
+
        strbuf_addch(&buf, '\n');
        sendline(data, &buf);
        strbuf_release(&buf);
+       string_list_clear(&cas_options, 0);
 
-       push_update_refs_status(data, remote_refs);
-       return 0;
+       return push_update_refs_status(data, remote_refs, flags);
 }
 
 static int push_refs_with_export(struct transport *transport,
@@ -733,43 +911,77 @@ static int push_refs_with_export(struct transport *transport,
        struct ref *ref;
        struct child_process *helper, exporter;
        struct helper_data *data = transport->data;
-       struct string_list revlist_args = STRING_LIST_INIT_NODUP;
+       struct string_list revlist_args = STRING_LIST_INIT_DUP;
        struct strbuf buf = STRBUF_INIT;
 
+       if (!data->refspecs)
+               die("remote-helper doesn't support push; refspec needed");
+
+       set_common_push_options(transport, data->name, flags);
+       if (flags & TRANSPORT_PUSH_FORCE) {
+               if (set_helper_option(transport, "force", "true") != 0)
+                       warning("helper %s does not support 'force'", data->name);
+       }
+
        helper = get_helper(transport);
 
        write_constant(helper->in, "export\n");
 
-       strbuf_reset(&buf);
-
        for (ref = remote_refs; ref; ref = ref->next) {
                char *private;
-               unsigned char sha1[20];
+               struct object_id oid;
 
-               if (!data->refspecs)
-                       continue;
                private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
-               if (private && !get_sha1(private, sha1)) {
+               if (private && !get_oid(private, &oid)) {
                        strbuf_addf(&buf, "^%s", private);
-                       string_list_append(&revlist_args, strbuf_detach(&buf, NULL));
+                       string_list_append_nodup(&revlist_args,
+                                                strbuf_detach(&buf, NULL));
+                       oidcpy(&ref->old_oid, &oid);
                }
                free(private);
 
-               if (ref->deletion) {
-                       die("remote-helpers do not support ref deletion");
+               if (ref->peer_ref) {
+                       if (strcmp(ref->name, ref->peer_ref->name)) {
+                               if (!ref->deletion) {
+                                       const char *name;
+                                       int flag;
+
+                                       /* Follow symbolic refs (mainly for HEAD). */
+                                       name = resolve_ref_unsafe(ref->peer_ref->name,
+                                                                 RESOLVE_REF_READING,
+                                                                 &oid, &flag);
+                                       if (!name || !(flag & REF_ISSYMREF))
+                                               name = ref->peer_ref->name;
+
+                                       strbuf_addf(&buf, "%s:%s", name, ref->name);
+                               } else
+                                       strbuf_addf(&buf, ":%s", ref->name);
+
+                               string_list_append(&revlist_args, "--refspec");
+                               string_list_append(&revlist_args, buf.buf);
+                               strbuf_release(&buf);
+                       }
+                       if (!ref->deletion)
+                               string_list_append(&revlist_args, ref->peer_ref->name);
                }
-
-               if (ref->peer_ref)
-                       string_list_append(&revlist_args, ref->peer_ref->name);
-
        }
 
        if (get_exporter(transport, &exporter, &revlist_args))
                die("Couldn't run fast-export");
 
+       string_list_clear(&revlist_args, 1);
+
        if (finish_command(&exporter))
                die("Error while running fast-export");
-       push_update_refs_status(data, remote_refs);
+       if (push_update_refs_status(data, remote_refs, flags))
+               return 1;
+
+       if (data->export_marks) {
+               strbuf_addf(&buf, "%s.tmp", data->export_marks);
+               rename(buf.buf, data->export_marks);
+               strbuf_release(&buf);
+       }
+
        return 0;
 }
 
@@ -780,7 +992,7 @@ static int push_refs(struct transport *transport,
 
        if (process_connect(transport, 1)) {
                do_take_over(transport);
-               return transport->push_refs(transport, remote_refs, flags);
+               return transport->vtable->push_refs(transport, remote_refs, flags);
        }
 
        if (!remote_refs) {
@@ -828,7 +1040,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
 
        if (process_connect(transport, for_push)) {
                do_take_over(transport);
-               return transport->get_refs_list(transport, for_push);
+               return transport->vtable->get_refs_list(transport, for_push);
        }
 
        if (data->push && for_push)
@@ -838,7 +1050,8 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
 
        while (1) {
                char *eov, *eon;
-               recvline(data, &buf);
+               if (recvline(data, &buf))
+                       exit(128);
 
                if (!*buf.buf)
                        break;
@@ -854,11 +1067,13 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
                if (buf.buf[0] == '@')
                        (*tail)->symref = xstrdup(buf.buf + 1);
                else if (buf.buf[0] != '?')
-                       get_sha1_hex(buf.buf, (*tail)->old_sha1);
+                       get_oid_hex(buf.buf, &(*tail)->old_oid);
                if (eon) {
                        if (has_attribute(eon + 1, "unchanged")) {
                                (*tail)->status |= REF_STATUS_UPTODATE;
-                               read_ref((*tail)->name, (*tail)->old_sha1);
+                               if (read_ref((*tail)->name, &(*tail)->old_oid) < 0)
+                                       die(_("Could not read ref %s"),
+                                           (*tail)->name);
                        }
                }
                tail = &((*tail)->next);
@@ -873,21 +1088,27 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
        return ret;
 }
 
+static struct transport_vtable vtable = {
+       set_helper_option,
+       get_refs_list,
+       fetch,
+       push_refs,
+       connect_helper,
+       release_helper
+};
+
 int transport_helper_init(struct transport *transport, const char *name)
 {
-       struct helper_data *data = xcalloc(sizeof(*data), 1);
+       struct helper_data *data = xcalloc(1, sizeof(*data));
        data->name = name;
 
+       transport_check_allowed(name);
+
        if (getenv("GIT_TRANSPORT_HELPER_DEBUG"))
                debug = 1;
 
        transport->data = data;
-       transport->set_option = set_helper_option;
-       transport->get_refs_list = get_refs_list;
-       transport->fetch = fetch;
-       transport->push_refs = push_refs;
-       transport->disconnect = release_helper;
-       transport->connect = connect_helper;
+       transport->vtable = &vtable;
        transport->smart_options = &(data->transport_options);
        return 0;
 }
@@ -901,8 +1122,16 @@ int transport_helper_init(struct transport *transport, const char *name)
 #define PBUFFERSIZE 8192
 
 /* Print bidirectional transfer loop debug message. */
+__attribute__((format (printf, 1, 2)))
 static void transfer_debug(const char *fmt, ...)
 {
+       /*
+        * NEEDSWORK: This function is sometimes used from multiple threads, and
+        * we end up using debug_enabled racily. That "should not matter" since
+        * we always write the same value, but it's still wrong. This function
+        * is listed in .tsan-suppressions for the time being.
+        */
+
        va_list args;
        char msgbuf[PBUFFERSIZE];
        static int debug_enabled = -1;
@@ -919,7 +1148,7 @@ static void transfer_debug(const char *fmt, ...)
 }
 
 /* Stream state: More data may be coming in this direction. */
-#define SSTATE_TRANSFERING 0
+#define SSTATE_TRANSFERRING 0
 /*
  * Stream state: No more data coming in this direction, flushing rest of
  * data.
@@ -928,7 +1157,7 @@ static void transfer_debug(const char *fmt, ...)
 /* Stream state: Transfer in this direction finished. */
 #define SSTATE_FINISHED 2
 
-#define STATE_NEEDS_READING(state) ((state) <= SSTATE_TRANSFERING)
+#define STATE_NEEDS_READING(state) ((state) <= SSTATE_TRANSFERRING)
 #define STATE_NEEDS_WRITING(state) ((state) <= SSTATE_FLUSHING)
 #define STATE_NEEDS_CLOSING(state) ((state) == SSTATE_FLUSHING)
 
@@ -942,7 +1171,7 @@ struct unidirectional_transfer {
        int src_is_sock;
        /* Is destination socket? */
        int dest_is_sock;
-       /* Transfer state (TRANSFERING/FLUSHING/FINISHED) */
+       /* Transfer state (TRANSFERRING/FLUSHING/FINISHED) */
        int state;
        /* Buffer. */
        char buf[BUFFERSIZE];
@@ -968,7 +1197,7 @@ static void udt_close_if_finished(struct unidirectional_transfer *t)
 }
 
 /*
- * Tries to read read data from source into buffer. If buffer is full,
+ * Tries to read data from source into buffer. If buffer is full,
  * no data is read. Returns 0 on success, -1 on error.
  */
 static int udt_do_read(struct unidirectional_transfer *t)
@@ -982,11 +1211,11 @@ static int udt_do_read(struct unidirectional_transfer *t)
        bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
        if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
                errno != EINTR) {
-               error("read(%s) failed: %s", t->src_name, strerror(errno));
+               error_errno("read(%s) failed", t->src_name);
                return -1;
        } else if (bytes == 0) {
                transfer_debug("%s EOF (with %i bytes in buffer)",
-                       t->src_name, t->bufuse);
+                       t->src_name, (int)t->bufuse);
                t->state = SSTATE_FLUSHING;
        } else if (bytes > 0) {
                t->bufuse += bytes;
@@ -1007,10 +1236,9 @@ static int udt_do_write(struct unidirectional_transfer *t)
                return 0;       /* Nothing to write. */
 
        transfer_debug("%s is writable", t->dest_name);
-       bytes = write(t->dest, t->buf, t->bufuse);
-       if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
-               errno != EINTR) {
-               error("write(%s) failed: %s", t->dest_name, strerror(errno));
+       bytes = xwrite(t->dest, t->buf, t->bufuse);
+       if (bytes < 0 && errno != EWOULDBLOCK) {
+               error_errno("write(%s) failed", t->dest_name);
                return -1;
        } else if (bytes > 0) {
                t->bufuse -= bytes;
@@ -1050,7 +1278,7 @@ static void *udt_copy_task_routine(void *udt)
 #ifndef NO_PTHREADS
 
 /*
- * Join thread, with apporiate errors on failure. Name is name for the
+ * Join thread, with appropriate errors on failure. Name is name for the
  * thread (for error messages). Returns 0 on success, 1 on failure.
  */
 static int tloop_join(pthread_t thread, const char *name)
@@ -1116,14 +1344,14 @@ static void udt_kill_transfer(struct unidirectional_transfer *t)
 }
 
 /*
- * Join process, with apporiate errors on failure. Name is name for the
+ * Join process, with appropriate errors on failure. Name is name for the
  * process (for error messages). Returns 0 on success, 1 on failure.
  */
 static int tloop_join(pid_t pid, const char *name)
 {
        int tret;
        if (waitpid(pid, &tret, 0) < 0) {
-               error("%s process failed to wait: %s", name, strerror(errno));
+               error_errno("%s process failed to wait", name);
                return 1;
        }
        if (!WIFEXITED(tret) || WEXITSTATUS(tret)) {
@@ -1186,7 +1414,7 @@ int bidirectional_transfer_loop(int input, int output)
        state.ptg.dest = 1;
        state.ptg.src_is_sock = (input == output);
        state.ptg.dest_is_sock = 0;
-       state.ptg.state = SSTATE_TRANSFERING;
+       state.ptg.state = SSTATE_TRANSFERRING;
        state.ptg.bufuse = 0;
        state.ptg.src_name = "remote input";
        state.ptg.dest_name = "stdout";
@@ -1195,7 +1423,7 @@ int bidirectional_transfer_loop(int input, int output)
        state.gtp.dest = output;
        state.gtp.src_is_sock = 0;
        state.gtp.dest_is_sock = (input == output);
-       state.gtp.state = SSTATE_TRANSFERING;
+       state.gtp.state = SSTATE_TRANSFERRING;
        state.gtp.bufuse = 0;
        state.gtp.src_name = "stdin";
        state.gtp.dest_name = "remote output";