Code

Merge branch 'tr/http-push-ref-status'
authorJunio C Hamano <gitster@pobox.com>
Wed, 20 Jan 2010 22:39:48 +0000 (14:39 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 20 Jan 2010 22:39:48 +0000 (14:39 -0800)
* tr/http-push-ref-status:
  transport-helper.c::push_refs(): emit "no refs" error message
  transport-helper.c::push_refs(): ignore helper-reported status if ref is not to be pushed
  transport.c::transport_push(): make ref status affect return value
  refactor ref status logic for pushing
  t5541-http-push.sh: add test for unmatched, non-fast-forwarded refs
  t5541-http-push.sh: add tests for non-fast-forward pushes

Conflicts:
transport-helper.c

1  2 
transport-helper.c
transport.c

diff --combined transport-helper.c
index ca8fa92e638e2589e2b6b8ffbe21ac824b5274a5,8c0b575f322c56c701448adc7acc976575e1ad98..fdf22562201d12d1321baae9157a0ca70053a7cb
@@@ -8,8 -8,6 +8,8 @@@
  #include "quote.h"
  #include "remote.h"
  
 +static int debug;
 +
  struct helper_data
  {
        const char *name;
        unsigned fetch : 1,
                import : 1,
                option : 1,
 -              push : 1;
 +              push : 1,
 +              connect : 1,
 +              no_disconnect_req : 1;
        /* These go from remote name (as in "list") to private name */
        struct refspec *refspecs;
        int refspec_nr;
 +      /* Transport options for fetch-pack/send-pack (should one of
 +       * those be invoked).
 +       */
 +      struct git_transport_options transport_options;
  };
  
 +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)
 +              die_errno("Full write to remote helper failed");
 +}
 +
 +static int recvline_fh(FILE *helper, struct strbuf *buffer)
 +{
 +      strbuf_reset(buffer);
 +      if (debug)
 +              fprintf(stderr, "Debug: Remote helper: Waiting...\n");
 +      if (strbuf_getline(buffer, helper, '\n') == EOF) {
 +              if (debug)
 +                      fprintf(stderr, "Debug: Remote helper quit.\n");
 +              exit(128);
 +      }
 +
 +      if (debug)
 +              fprintf(stderr, "Debug: Remote helper: <- %s\n", buffer->buf);
 +      return 0;
 +}
 +
 +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);
 +}
 +
 +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))
 +              die_errno("Full write to remote helper failed");
 +}
 +
 +const char *remove_ext_force(const char *url)
 +{
 +      if (url) {
 +              const char *colon = strchr(url, ':');
 +              if (colon && colon[1] == ':')
 +                      return colon + 2;
 +      }
 +      return url;
 +}
 +
 +static void do_take_over(struct transport *transport)
 +{
 +      struct helper_data *data;
 +      data = (struct helper_data *)transport->data;
 +      transport_take_over(transport, data->helper);
 +      fclose(data->out);
 +      free(data);
 +}
 +
  static struct child_process *get_helper(struct transport *transport)
  {
        struct helper_data *data = transport->data;
        const char **refspecs = NULL;
        int refspec_nr = 0;
        int refspec_alloc = 0;
 +      int duped;
  
        if (data->helper)
                return data->helper;
        strbuf_addf(&buf, "remote-%s", data->name);
        helper->argv[0] = strbuf_detach(&buf, NULL);
        helper->argv[1] = transport->remote->name;
 -      helper->argv[2] = transport->url;
 +      helper->argv[2] = remove_ext_force(transport->url);
        helper->git_cmd = 1;
        if (start_command(helper))
                die("Unable to run helper: git %s", helper->argv[0]);
        data->helper = helper;
 +      data->no_disconnect_req = 0;
 +
 +      /*
 +       * Open the output as FILE* so strbuf_getline() 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.
 +       */
 +      duped = dup(helper->out);
 +      if (duped < 0)
 +              die_errno("Can't dup helper output fd");
 +      data->out = xfdopen(duped, "r");
  
 -      write_str_in_full(helper->in, "capabilities\n");
 +      write_constant(helper->in, "capabilities\n");
  
 -      data->out = xfdopen(helper->out, "r");
        while (1) {
 -              if (strbuf_getline(&buf, data->out, '\n') == EOF)
 -                      exit(128); /* child died, message supplied already */
 +              const char *capname;
 +              int mandatory = 0;
 +              recvline(data, &buf);
  
                if (!*buf.buf)
                        break;
 -              if (!strcmp(buf.buf, "fetch"))
 +
 +              if (*buf.buf == '*') {
 +                      capname = buf.buf + 1;
 +                      mandatory = 1;
 +              } else
 +                      capname = buf.buf;
 +
 +              if (debug)
 +                      fprintf(stderr, "Debug: Got cap %s\n", capname);
 +              if (!strcmp(capname, "fetch"))
                        data->fetch = 1;
 -              if (!strcmp(buf.buf, "option"))
 +              else if (!strcmp(capname, "option"))
                        data->option = 1;
 -              if (!strcmp(buf.buf, "push"))
 +              else if (!strcmp(capname, "push"))
                        data->push = 1;
 -              if (!strcmp(buf.buf, "import"))
 +              else if (!strcmp(capname, "import"))
                        data->import = 1;
 -              if (!data->refspecs && !prefixcmp(buf.buf, "refspec ")) {
 +              else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
                        ALLOC_GROW(refspecs,
                                   refspec_nr + 1,
                                   refspec_alloc);
                        refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
 +              } else if (!strcmp(capname, "connect")) {
 +                      data->connect = 1;
 +              } else if (mandatory) {
 +                      die("Unknown madatory capability %s. This remote "
 +                          "helper probably needs newer version of Git.\n",
 +                          capname);
                }
        }
        if (refspecs) {
                free(refspecs);
        }
        strbuf_release(&buf);
 +      if (debug)
 +              fprintf(stderr, "Debug: Capabilities complete.\n");
        return data->helper;
  }
  
  static int disconnect_helper(struct transport *transport)
  {
        struct helper_data *data = transport->data;
 +      struct strbuf buf = STRBUF_INIT;
 +
        if (data->helper) {
 -              write_str_in_full(data->helper->in, "\n");
 +              if (debug)
 +                      fprintf(stderr, "Debug: Disconnecting.\n");
 +              if (!data->no_disconnect_req) {
 +                      strbuf_addf(&buf, "\n");
 +                      sendline(data, &buf);
 +              }
                close(data->helper->in);
 +              close(data->helper->out);
                fclose(data->out);
                finish_command(data->helper);
                free((char *)data->helper->argv[0]);
@@@ -225,11 -117,10 +225,11 @@@ static int set_helper_option(struct tra
                          const char *name, const char *value)
  {
        struct helper_data *data = transport->data;
 -      struct child_process *helper = get_helper(transport);
        struct strbuf buf = STRBUF_INIT;
        int i, ret, is_bool = 0;
  
 +      get_helper(transport);
 +
        if (!data->option)
                return 1;
  
                quote_c_style(value, &buf, NULL, 0);
        strbuf_addch(&buf, '\n');
  
 -      if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
 -              die_errno("cannot send option to %s", data->name);
 -
 -      strbuf_reset(&buf);
 -      if (strbuf_getline(&buf, data->out, '\n') == EOF)
 -              exit(128); /* child died, message supplied already */
 +      xchgline(data, &buf);
  
        if (!strcmp(buf.buf, "ok"))
                ret = 0;
@@@ -273,7 -169,7 +273,7 @@@ static void standard_options(struct tra
        char buf[16];
        int n;
        int v = t->verbose;
 -      int no_progress = v < 0 || (!t->progress && !isatty(1));
 +      int no_progress = v < 0 || (!t->progress && !isatty(2));
  
        set_helper_option(t, "progress", !no_progress ? "true" : "false");
  
@@@ -312,10 -208,13 +312,10 @@@ static int fetch_with_fetch(struct tran
        }
  
        strbuf_addch(&buf, '\n');
 -      if (write_in_full(data->helper->in, buf.buf, buf.len) != buf.len)
 -              die_errno("cannot send fetch to %s", data->name);
 +      sendline(data, &buf);
  
        while (1) {
 -              strbuf_reset(&buf);
 -              if (strbuf_getline(&buf, data->out, '\n') == EOF)
 -                      exit(128); /* child died, message supplied already */
 +              recvline(data, &buf);
  
                if (!prefixcmp(buf.buf, "lock ")) {
                        const char *name = buf.buf + 5;
@@@ -350,13 -249,12 +350,13 @@@ static int fetch_with_import(struct tra
                             int nr_heads, struct ref **to_fetch)
  {
        struct child_process fastimport;
 -      struct child_process *helper = get_helper(transport);
        struct helper_data *data = transport->data;
        int i;
        struct ref *posn;
        struct strbuf buf = STRBUF_INIT;
  
 +      get_helper(transport);
 +
        if (get_importer(transport, &fastimport))
                die("Couldn't run fast-import");
  
                        continue;
  
                strbuf_addf(&buf, "import %s\n", posn->name);
 -              write_in_full(helper->in, buf.buf, buf.len);
 +              sendline(data, &buf);
                strbuf_reset(&buf);
        }
        disconnect_helper(transport);
        return 0;
  }
  
 +static int process_connect_service(struct transport *transport,
 +                                 const char *name, const char *exec)
 +{
 +      struct helper_data *data = transport->data;
 +      struct strbuf cmdbuf = STRBUF_INIT;
 +      struct child_process *helper;
 +      int r, duped, ret = 0;
 +      FILE *input;
 +
 +      helper = get_helper(transport);
 +
 +      /*
 +       * Yes, dup the pipe another time, as we need unbuffered version
 +       * of input pipe as FILE*. fclose() closes the underlying fd and
 +       * stream buffering only can be changed before first I/O operation
 +       * on it.
 +       */
 +      duped = dup(helper->out);
 +      if (duped < 0)
 +              die_errno("Can't dup helper output fd");
 +      input = xfdopen(duped, "r");
 +      setvbuf(input, NULL, _IONBF, 0);
 +
 +      /*
 +       * Handle --upload-pack and friends. This is fire and forget...
 +       * just warn if it fails.
 +       */
 +      if (strcmp(name, exec)) {
 +              r = set_helper_option(transport, "servpath", exec);
 +              if (r > 0)
 +                      warning("Setting remote service path not supported by protocol.");
 +              else if (r < 0)
 +                      warning("Invalid remote service path.");
 +      }
 +
 +      if (data->connect)
 +              strbuf_addf(&cmdbuf, "connect %s\n", name);
 +      else
 +              goto exit;
 +
 +      sendline(data, &cmdbuf);
 +      recvline_fh(input, &cmdbuf);
 +      if (!strcmp(cmdbuf.buf, "")) {
 +              data->no_disconnect_req = 1;
 +              if (debug)
 +                      fprintf(stderr, "Debug: Smart transport connection "
 +                              "ready.\n");
 +              ret = 1;
 +      } else if (!strcmp(cmdbuf.buf, "fallback")) {
 +              if (debug)
 +                      fprintf(stderr, "Debug: Falling back to dumb "
 +                              "transport.\n");
 +      } else
 +              die("Unknown response to connect: %s",
 +                      cmdbuf.buf);
 +
 +exit:
 +      fclose(input);
 +      return ret;
 +}
 +
 +static int process_connect(struct transport *transport,
 +                                   int for_push)
 +{
 +      struct helper_data *data = transport->data;
 +      const char *name;
 +      const char *exec;
 +
 +      name = for_push ? "git-receive-pack" : "git-upload-pack";
 +      if (for_push)
 +              exec = data->transport_options.receivepack;
 +      else
 +              exec = data->transport_options.uploadpack;
 +
 +      return process_connect_service(transport, name, exec);
 +}
 +
 +static int connect_helper(struct transport *transport, const char *name,
 +                 const char *exec, int fd[2])
 +{
 +      struct helper_data *data = transport->data;
 +
 +      /* Get_helper so connect is inited. */
 +      get_helper(transport);
 +      if (!data->connect)
 +              die("Operation not supported by protocol.");
 +
 +      if (!process_connect_service(transport, name, exec))
 +              die("Can't connect to subservice %s.", name);
 +
 +      fd[0] = data->helper->out;
 +      fd[1] = data->helper->in;
 +      return 0;
 +}
 +
  static int fetch(struct transport *transport,
                 int nr_heads, struct ref **to_fetch)
  {
        struct helper_data *data = transport->data;
        int i, count;
  
 +      if (process_connect(transport, 0)) {
 +              do_take_over(transport);
 +              return transport->fetch(transport, nr_heads, to_fetch);
 +      }
 +
        count = 0;
        for (i = 0; i < nr_heads; i++)
                if (!(to_fetch[i]->status & REF_STATUS_UPTODATE))
@@@ -523,29 -321,27 +523,32 @@@ static int push_refs(struct transport *
        struct child_process *helper;
        struct ref *ref;
  
-       if (!remote_refs)
 +      if (process_connect(transport, 1)) {
 +              do_take_over(transport);
 +              return transport->push_refs(transport, remote_refs, flags);
 +      }
 +
+       if (!remote_refs) {
+               fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
+                       "Perhaps you should specify a branch such as 'master'.\n");
                return 0;
+       }
  
        helper = get_helper(transport);
        if (!data->push)
                return 1;
  
        for (ref = remote_refs; ref; ref = ref->next) {
-               if (ref->peer_ref)
-                       hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               else if (!mirror)
+               if (!ref->peer_ref && !mirror)
                        continue;
  
-               ref->deletion = is_null_sha1(ref->new_sha1);
-               if (!ref->deletion &&
-                       !hashcmp(ref->old_sha1, ref->new_sha1)) {
-                       ref->status = REF_STATUS_UPTODATE;
+               /* Check for statuses set by set_ref_status_for_push() */
+               switch (ref->status) {
+               case REF_STATUS_REJECT_NONFASTFORWARD:
+               case REF_STATUS_UPTODATE:
                        continue;
+               default:
+                       ; /* do nothing */
                }
  
                if (force_all)
        }
  
        strbuf_addch(&buf, '\n');
 -      if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
 -              exit(128);
 +      sendline(data, &buf);
  
        ref = remote_refs;
        while (1) {
                char *refname, *msg;
                int status;
  
 -              strbuf_reset(&buf);
 -              if (strbuf_getline(&buf, data->out, '\n') == EOF)
 -                      exit(128); /* child died, message supplied already */
 +              recvline(data, &buf);
                if (!buf.len)
                        break;
  
                        continue;
                }
  
+               if (ref->status != REF_STATUS_NONE) {
+                       /*
+                        * Earlier, the ref was marked not to be pushed, so ignore the ref
+                        * status reported by the remote helper if the latter is 'no match'.
+                        */
+                       if (status == REF_STATUS_NONE)
+                               continue;
+               }
                ref->status = status;
                ref->remote_status = msg;
        }
@@@ -668,11 -476,6 +680,11 @@@ static struct ref *get_refs_list(struc
  
        helper = get_helper(transport);
  
 +      if (process_connect(transport, for_push)) {
 +              do_take_over(transport);
 +              return transport->get_refs_list(transport, for_push);
 +      }
 +
        if (data->push && for_push)
                write_str_in_full(helper->in, "list for-push\n");
        else
  
        while (1) {
                char *eov, *eon;
 -              if (strbuf_getline(&buf, data->out, '\n') == EOF)
 -                      exit(128); /* child died, message supplied already */
 +              recvline(data, &buf);
  
                if (!*buf.buf)
                        break;
                }
                tail = &((*tail)->next);
        }
 +      if (debug)
 +              fprintf(stderr, "Debug: Read ref listing.\n");
        strbuf_release(&buf);
  
        for (posn = ret; posn; posn = posn->next)
@@@ -720,16 -522,11 +732,16 @@@ int transport_helper_init(struct transp
        struct helper_data *data = xcalloc(sizeof(*data), 1);
        data->name = 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->smart_options = &(data->transport_options);
        return 0;
  }
diff --combined transport.c
index c3f156ea04636a722a49aca225175a6f9e9259c9,9b23989117da9d538b8221b02963bbcd17784d6c..d7db2941fb135c8d956756ced762dc0a3dd75a4b
@@@ -143,7 -143,7 +143,7 @@@ static const char *rsync_url(const cha
  static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
  {
        struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;
 -      struct ref dummy, *tail = &dummy;
 +      struct ref dummy = {0}, *tail = &dummy;
        struct child_process rsync;
        const char *args[5];
        int temp_dir_len;
@@@ -395,36 -395,41 +395,36 @@@ static int close_bundle(struct transpor
  }
  
  struct git_transport_data {
 -      unsigned thin : 1;
 -      unsigned keep : 1;
 -      unsigned followtags : 1;
 -      int depth;
 +      struct git_transport_options options;
        struct child_process *conn;
        int fd[2];
 -      const char *uploadpack;
 -      const char *receivepack;
 +      unsigned got_remote_heads : 1;
        struct extra_have_objects extra_have;
  };
  
 -static int set_git_option(struct transport *connection,
 +static int set_git_option(struct git_transport_options *opts,
                          const char *name, const char *value)
  {
 -      struct git_transport_data *data = connection->data;
        if (!strcmp(name, TRANS_OPT_UPLOADPACK)) {
 -              data->uploadpack = value;
 +              opts->uploadpack = value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {
 -              data->receivepack = value;
 +              opts->receivepack = value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_THIN)) {
 -              data->thin = !!value;
 +              opts->thin = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) {
 -              data->followtags = !!value;
 +              opts->followtags = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_KEEP)) {
 -              data->keep = !!value;
 +              opts->keep = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_DEPTH)) {
                if (!value)
 -                      data->depth = 0;
 +                      opts->depth = 0;
                else
 -                      data->depth = atoi(value);
 +                      opts->depth = atoi(value);
                return 0;
        }
        return 1;
  static int connect_setup(struct transport *transport, int for_push, int verbose)
  {
        struct git_transport_data *data = transport->data;
 +
 +      if (data->conn)
 +              return 0;
 +
        data->conn = git_connect(data->fd, transport->url,
 -                               for_push ? data->receivepack : data->uploadpack,
 +                               for_push ? data->options.receivepack :
 +                               data->options.uploadpack,
                                 verbose ? CONNECT_VERBOSE : 0);
 +
        return 0;
  }
  
@@@ -453,7 -452,6 +453,7 @@@ static struct ref *get_refs_via_connect
        connect_setup(transport, for_push, 0);
        get_remote_heads(data->fd[0], &refs, 0, NULL,
                         for_push ? REF_NORMAL : 0, &data->extra_have);
 +      data->got_remote_heads = 1;
  
        return refs;
  }
@@@ -471,23 -469,22 +471,23 @@@ static int fetch_refs_via_pack(struct t
        struct ref *refs_tmp = NULL;
  
        memset(&args, 0, sizeof(args));
 -      args.uploadpack = data->uploadpack;
 -      args.keep_pack = data->keep;
 +      args.uploadpack = data->options.uploadpack;
 +      args.keep_pack = data->options.keep;
        args.lock_pack = 1;
 -      args.use_thin_pack = data->thin;
 -      args.include_tag = data->followtags;
 +      args.use_thin_pack = data->options.thin;
 +      args.include_tag = data->options.followtags;
        args.verbose = (transport->verbose > 0);
        args.quiet = (transport->verbose < 0);
 -      args.no_progress = args.quiet || (!transport->progress && !isatty(1));
 -      args.depth = data->depth;
 +      args.no_progress = args.quiet || (!transport->progress && !isatty(2));
 +      args.depth = data->options.depth;
  
        for (i = 0; i < nr_heads; i++)
                origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
  
 -      if (!data->conn) {
 +      if (!data->got_remote_heads) {
                connect_setup(transport, 0, 0);
                get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
 +              data->got_remote_heads = 1;
        }
  
        refs = fetch_pack(&args, data->fd, data->conn,
        if (finish_connect(data->conn))
                refs = NULL;
        data->conn = NULL;
 +      data->got_remote_heads = 0;
  
        free_refs(refs_tmp);
  
@@@ -727,19 -723,18 +727,19 @@@ static int git_transport_push(struct tr
        struct send_pack_args args;
        int ret;
  
 -      if (!data->conn) {
 +      if (!data->got_remote_heads) {
                struct ref *tmp_refs;
                connect_setup(transport, 1, 0);
  
                get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
                                 NULL);
 +              data->got_remote_heads = 1;
        }
  
        memset(&args, 0, sizeof(args));
        args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
        args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
 -      args.use_thin_pack = data->thin;
 +      args.use_thin_pack = data->options.thin;
        args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
        args.quiet = !!(flags & TRANSPORT_PUSH_QUIET);
        args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
        close(data->fd[0]);
        ret |= finish_connect(data->conn);
        data->conn = NULL;
 +      data->got_remote_heads = 0;
  
        return ret;
  }
  
 +static int connect_git(struct transport *transport, const char *name,
 +                     const char *executable, int fd[2])
 +{
 +      struct git_transport_data *data = transport->data;
 +      data->conn = git_connect(data->fd, transport->url,
 +                               executable, 0);
 +      fd[0] = data->fd[0];
 +      fd[1] = data->fd[1];
 +      return 0;
 +}
 +
  static int disconnect_git(struct transport *transport)
  {
        struct git_transport_data *data = transport->data;
        if (data->conn) {
 -              packet_flush(data->fd[1]);
 +              if (data->got_remote_heads)
 +                      packet_flush(data->fd[1]);
                close(data->fd[0]);
                close(data->fd[1]);
                finish_connect(data->conn);
        return 0;
  }
  
 +void transport_take_over(struct transport *transport,
 +                       struct child_process *child)
 +{
 +      struct git_transport_data *data;
 +
 +      if (!transport->smart_options)
 +              die("Bug detected: Taking over transport requires non-NULL "
 +                  "smart_options field.");
 +
 +      data = xcalloc(1, sizeof(*data));
 +      data->options = *transport->smart_options;
 +      data->conn = child;
 +      data->fd[0] = data->conn->out;
 +      data->fd[1] = data->conn->in;
 +      data->got_remote_heads = 0;
 +      transport->data = data;
 +
 +      transport->set_option = NULL;
 +      transport->get_refs_list = get_refs_via_connect;
 +      transport->fetch = fetch_refs_via_pack;
 +      transport->push = NULL;
 +      transport->push_refs = git_transport_push;
 +      transport->disconnect = disconnect_git;
 +      transport->smart_options = &(data->options);
 +}
 +
  static int is_local(const char *url)
  {
        const char *colon = strchr(url, ':');
@@@ -824,44 -780,6 +824,44 @@@ static int is_file(const char *url
        return S_ISREG(buf.st_mode);
  }
  
 +static int is_url(const char *url)
 +{
 +      const char *url2, *first_slash;
 +
 +      if (!url)
 +              return 0;
 +      url2 = url;
 +      first_slash = strchr(url, '/');
 +
 +      /* Input with no slash at all or slash first can't be URL. */
 +      if (!first_slash || first_slash == url)
 +              return 0;
 +      /* Character before must be : and next must be /. */
 +      if (first_slash[-1] != ':' || first_slash[1] != '/')
 +              return 0;
 +      /* There must be something before the :// */
 +      if (first_slash == url + 1)
 +              return 0;
 +      /*
 +       * Check all characters up to first slash - 1. Only alphanum
 +       * is allowed.
 +       */
 +      url2 = url;
 +      while (url2 < first_slash - 1) {
 +              if (!isalnum((unsigned char)*url2))
 +                      return 0;
 +              url2++;
 +      }
 +
 +      /* Valid enough. */
 +      return 1;
 +}
 +
 +static int external_specification_len(const char *url)
 +{
 +      return strchr(url, ':') - url;
 +}
 +
  struct transport *transport_get(struct remote *remote, const char *url)
  {
        struct transport *ret = xcalloc(1, sizeof(*ret));
                url = remote->url[0];
        ret->url = url;
  
 +      /* In case previous URL had helper forced, reset it. */
 +      remote->foreign_vcs = NULL;
 +
        /* maybe it is a foreign URL? */
        if (url) {
                const char *p = url;
  
        if (remote && remote->foreign_vcs) {
                transport_helper_init(ret, remote->foreign_vcs);
 -              return ret;
 -      }
 -
 -      if (!prefixcmp(url, "rsync:")) {
 +      } else if (!prefixcmp(url, "rsync:")) {
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
 -
 -      } else if (!prefixcmp(url, "http://")
 -              || !prefixcmp(url, "https://")
 -              || !prefixcmp(url, "ftp://")) {
 -              transport_helper_init(ret, "curl");
 -#ifdef NO_CURL
 -              error("git was compiled without libcurl support.");
 -#endif
 -
 +              ret->smart_options = NULL;
        } else if (is_local(url) && is_file(url)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
                ret->fetch = fetch_refs_from_bundle;
                ret->disconnect = close_bundle;
 -
 -      } else {
 +              ret->smart_options = NULL;
 +      } else if (!is_url(url)
 +              || !prefixcmp(url, "file://")
 +              || !prefixcmp(url, "git://")
 +              || !prefixcmp(url, "ssh://")
 +              || !prefixcmp(url, "git+ssh://")
 +              || !prefixcmp(url, "ssh+git://")) {
 +              /* These are builtin smart transports. */
                struct git_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
 -              ret->set_option = set_git_option;
 +              ret->set_option = NULL;
                ret->get_refs_list = get_refs_via_connect;
                ret->fetch = fetch_refs_via_pack;
                ret->push_refs = git_transport_push;
 +              ret->connect = connect_git;
                ret->disconnect = disconnect_git;
 +              ret->smart_options = &(data->options);
  
 -              data->thin = 1;
                data->conn = NULL;
 -              data->uploadpack = "git-upload-pack";
 +              data->got_remote_heads = 0;
 +      } else {
 +              /* Unknown protocol in URL. Pass to external handler. */
 +              int len = external_specification_len(url);
 +              char *handler = xmalloc(len + 1);
 +              handler[len] = 0;
 +              strncpy(handler, url, len);
 +              transport_helper_init(ret, handler);
 +      }
 +
 +      if (ret->smart_options) {
 +              ret->smart_options->thin = 1;
 +              ret->smart_options->uploadpack = "git-upload-pack";
                if (remote->uploadpack)
 -                      data->uploadpack = remote->uploadpack;
 -              data->receivepack = "git-receive-pack";
 +                      ret->smart_options->uploadpack = remote->uploadpack;
 +              ret->smart_options->receivepack = "git-receive-pack";
                if (remote->receivepack)
 -                      data->receivepack = remote->receivepack;
 +                      ret->smart_options->receivepack = remote->receivepack;
        }
  
        return ret;
  int transport_set_option(struct transport *transport,
                         const char *name, const char *value)
  {
 +      int git_reports = 1, protocol_reports = 1;
 +
 +      if (transport->smart_options)
 +              git_reports = set_git_option(transport->smart_options,
 +                                           name, value);
 +
        if (transport->set_option)
 -              return transport->set_option(transport, name, value);
 +              protocol_reports = transport->set_option(transport, name,
 +                                                      value);
 +
 +      /* If either report is 0, report 0 (success). */
 +      if (!git_reports || !protocol_reports)
 +              return 0;
 +      /* If either reports -1 (invalid value), report -1. */
 +      if ((git_reports == -1) || (protocol_reports == -1))
 +              return -1;
 +      /* Otherwise if both report unknown, report unknown. */
        return 1;
  }
  
@@@ -973,9 -865,9 +973,9 @@@ int transport_push(struct transport *tr
        *nonfastforward = 0;
        verify_remote_names(refspec_nr, refspec);
  
 -      if (transport->push)
 +      if (transport->push) {
                return transport->push(transport, refspec_nr, refspec, flags);
 -      if (transport->push_refs) {
 +      } else if (transport->push_refs) {
                struct ref *remote_refs =
                        transport->get_refs_list(transport, 1);
                struct ref *local_refs = get_local_heads();
                int verbose = flags & TRANSPORT_PUSH_VERBOSE;
                int quiet = flags & TRANSPORT_PUSH_QUIET;
                int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
-               int ret;
+               int ret, err;
  
                if (flags & TRANSPORT_PUSH_ALL)
                        match_flags |= MATCH_REFS_ALL;
                        return -1;
                }
  
+               set_ref_status_for_push(remote_refs,
+                       flags & TRANSPORT_PUSH_MIRROR,
+                       flags & TRANSPORT_PUSH_FORCE);
                ret = transport->push_refs(transport, remote_refs, flags);
+               err = push_had_errors(remote_refs);
+               ret |= err;
  
-               if (!quiet || push_had_errors(remote_refs))
+               if (!quiet || err)
                        print_push_status(transport->url, remote_refs,
                                        verbose | porcelain, porcelain,
                                        nonfastforward);
@@@ -1019,7 -918,6 +1026,7 @@@ const struct ref *transport_get_remote_
  {
        if (!transport->remote_refs)
                transport->remote_refs = transport->get_refs_list(transport, 0);
 +
        return transport->remote_refs;
  }
  
@@@ -1054,7 -952,6 +1061,7 @@@ int transport_fetch_refs(struct transpo
        }
  
        rc = transport->fetch(transport, nr_heads, heads);
 +
        free(heads);
        return rc;
  }
@@@ -1068,15 -965,6 +1075,15 @@@ void transport_unlock_pack(struct trans
        }
  }
  
 +int transport_connect(struct transport *transport, const char *name,
 +                    const char *exec, int fd[2])
 +{
 +      if (transport->connect)
 +              return transport->connect(transport, name, exec, fd);
 +      else
 +              die("Operation not supported by protocol");
 +}
 +
  int transport_disconnect(struct transport *transport)
  {
        int ret = 0;