Merge branch 'tc/clone-v-progress'
authorJunio C Hamano <gitster@pobox.com>
Sun, 17 Jan 2010 23:58:58 +0000 (15:58 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 17 Jan 2010 23:58:58 +0000 (15:58 -0800)
* tc/clone-v-progress:
  clone: use --progress to force progress reporting
  clone: set transport->verbose when -v/--verbose is used
  git-clone.txt: reword description of progress behaviour
  check stderr with isatty() instead of stdout when deciding to show progress

Conflicts:
transport.c

1  2 
builtin-clone.c
transport-helper.c
transport.c
transport.h

diff --combined builtin-clone.c
@@@ -44,10 -44,13 +44,13 @@@ static char *option_origin = NULL
  static char *option_branch = NULL;
  static char *option_upload_pack = "git-upload-pack";
  static int option_verbose;
+ static int option_progress;
  
  static struct option builtin_clone_options[] = {
        OPT__QUIET(&option_quiet),
        OPT__VERBOSE(&option_verbose),
+       OPT_BOOLEAN(0, "progress", &option_progress,
+                       "force progress reporting"),
        OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
                    "don't create a checkout"),
        OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
@@@ -362,10 -365,9 +365,10 @@@ int cmd_clone(int argc, const char **ar
        const char *repo_name, *repo, *work_tree, *git_dir;
        char *path, *dir;
        int dest_exists;
 -      const struct ref *refs, *remote_head, *mapped_refs;
 +      const struct ref *refs, *remote_head;
        const struct ref *remote_head_points_at;
        const struct ref *our_head_points_at;
 +      struct ref *mapped_refs;
        struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
                if (option_quiet)
                        transport->verbose = -1;
                else if (option_verbose)
+                       transport->verbose = 1;
+               if (option_progress)
                        transport->progress = 1;
  
                if (option_upload_pack)
diff --combined transport-helper.c
@@@ -6,9 -6,6 +6,9 @@@
  #include "diff.h"
  #include "revision.h"
  #include "quote.h"
 +#include "remote.h"
 +
 +static int debug;
  
  struct helper_data
  {
        struct child_process *helper;
        FILE *out;
        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;
        struct strbuf buf = STRBUF_INIT;
        struct child_process *helper;
 +      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;
  
 -      write_str_in_full(helper->in, "capabilities\n");
 +      /*
 +       * 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_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;
 +              else if (!strcmp(capname, "import"))
 +                      data->import = 1;
 +              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) {
 +              int i;
 +              data->refspec_nr = refspec_nr;
 +              data->refspecs = parse_fetch_refspec(refspec_nr, refspecs);
 +              for (i = 0; i < refspec_nr; i++) {
 +                      free((char *)refspecs[i]);
 +              }
 +              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]);
                free(data->helper);
                data->helper = NULL;
        }
 -      free(data);
        return 0;
  }
  
@@@ -225,11 -92,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 -144,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");
  
        set_helper_option(t, "verbosity", buf);
  }
  
 +static int release_helper(struct transport *transport)
 +{
 +      struct helper_data *data = transport->data;
 +      free_refspec(data->refspec_nr, data->refspecs);
 +      data->refspecs = NULL;
 +      disconnect_helper(transport);
 +      free(transport->data);
 +      return 0;
 +}
 +
  static int fetch_with_fetch(struct transport *transport,
 -                          int nr_heads, const struct ref **to_fetch)
 +                          int nr_heads, struct ref **to_fetch)
  {
        struct helper_data *data = transport->data;
        int i;
        }
  
        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;
        return 0;
  }
  
 +static int get_importer(struct transport *transport, struct child_process *fastimport)
 +{
 +      struct child_process *helper = get_helper(transport);
 +      memset(fastimport, 0, sizeof(*fastimport));
 +      fastimport->in = helper->out;
 +      fastimport->argv = xcalloc(5, sizeof(*fastimport->argv));
 +      fastimport->argv[0] = "fast-import";
 +      fastimport->argv[1] = "--quiet";
 +
 +      fastimport->git_cmd = 1;
 +      return start_command(fastimport);
 +}
 +
 +static int fetch_with_import(struct transport *transport,
 +                           int nr_heads, struct ref **to_fetch)
 +{
 +      struct child_process fastimport;
 +      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");
 +
 +      for (i = 0; i < nr_heads; i++) {
 +              posn = to_fetch[i];
 +              if (posn->status & REF_STATUS_UPTODATE)
 +                      continue;
 +
 +              strbuf_addf(&buf, "import %s\n", posn->name);
 +              sendline(data, &buf);
 +              strbuf_reset(&buf);
 +      }
 +      disconnect_helper(transport);
 +      finish_command(&fastimport);
 +      free(fastimport.argv);
 +      fastimport.argv = NULL;
 +
 +      for (i = 0; i < nr_heads; i++) {
 +              char *private;
 +              posn = to_fetch[i];
 +              if (posn->status & REF_STATUS_UPTODATE)
 +                      continue;
 +              if (data->refspecs)
 +                      private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name);
 +              else
 +                      private = strdup(posn->name);
 +              read_ref(private, posn->old_sha1);
 +              free(private);
 +      }
 +      strbuf_release(&buf);
 +      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, const struct ref **to_fetch)
 +               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))
        if (data->fetch)
                return fetch_with_fetch(transport, nr_heads, to_fetch);
  
 +      if (data->import)
 +              return fetch_with_import(transport, nr_heads, to_fetch);
 +
        return -1;
  }
  
@@@ -523,11 -227,6 +523,11 @@@ static int push_refs(struct transport *
        struct child_process *helper;
        struct ref *ref;
  
 +      if (process_connect(transport, 1)) {
 +              do_take_over(transport);
 +              return transport->push_refs(transport, remote_refs, flags);
 +      }
 +
        if (!remote_refs)
                return 0;
  
        }
  
        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;
  
        return 0;
  }
  
 +static int has_attribute(const char *attrs, const char *attr) {
 +      int len;
 +      if (!attrs)
 +              return 0;
 +
 +      len = strlen(attr);
 +      for (;;) {
 +              const char *space = strchrnul(attrs, ' ');
 +              if (len == space - attrs && !strncmp(attrs, attr, len))
 +                      return 1;
 +              if (!*space)
 +                      return 0;
 +              attrs = space + 1;
 +      }
 +}
 +
  static struct ref *get_refs_list(struct transport *transport, int for_push)
  {
        struct helper_data *data = transport->data;
  
        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)->symref = xstrdup(buf.buf + 1);
                else if (buf.buf[0] != '?')
                        get_sha1_hex(buf.buf, (*tail)->old_sha1);
 +              if (eon) {
 +                      if (has_attribute(eon + 1, "unchanged")) {
 +                              (*tail)->status |= REF_STATUS_UPTODATE;
 +                              read_ref((*tail)->name, (*tail)->old_sha1);
 +                      }
 +              }
                tail = &((*tail)->next);
        }
 +      if (debug)
 +              fprintf(stderr, "Debug: Read ref listing.\n");
        strbuf_release(&buf);
  
        for (posn = ret; posn; posn = posn->next)
@@@ -720,16 -394,11 +720,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 = disconnect_helper;
 +      transport->disconnect = release_helper;
 +      transport->connect = connect_helper;
 +      transport->smart_options = &(data->transport_options);
        return 0;
  }
diff --combined transport.c
@@@ -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;
  }
  
  static int fetch_objs_via_rsync(struct transport *transport,
 -                              int nr_objs, const struct ref **to_fetch)
 +                              int nr_objs, struct ref **to_fetch)
  {
        struct strbuf buf = STRBUF_INIT;
        struct child_process rsync;
@@@ -379,7 -379,7 +379,7 @@@ static struct ref *get_refs_from_bundle
  }
  
  static int fetch_refs_from_bundle(struct transport *transport,
 -                             int nr_heads, const struct ref **to_fetch)
 +                             int nr_heads, struct ref **to_fetch)
  {
        struct bundle_transport_data *data = transport->data;
        return unbundle(&data->header, data->fd);
@@@ -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,13 -452,12 +453,13 @@@ 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;
  }
  
  static int fetch_refs_via_pack(struct transport *transport,
 -                             int nr_heads, const struct ref **to_fetch)
 +                             int nr_heads, struct ref **to_fetch)
  {
        struct git_transport_data *data = transport->data;
        char **heads = xmalloc(nr_heads * sizeof(*heads));
        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.no_progress = args.quiet || (!transport->progress && !isatty(2));
 -      args.depth = data->depth;
 +      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));
                die("No remote provided to transport_get()");
  
        ret->remote = remote;
 +
 +      if (!url && remote && remote->url)
 +              url = remote->url[0];
        ret->url = url;
  
 -      if (!prefixcmp(url, "rsync:")) {
 +      /* 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;
 +
 +              while (isalnum(*p))
 +                      p++;
 +              if (!prefixcmp(p, "::"))
 +                      remote->foreign_vcs = xstrndup(url, p - url);
 +      }
 +
 +      if (remote && remote->foreign_vcs) {
 +              transport_helper_init(ret, remote->foreign_vcs);
 +      } 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 -847,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();
@@@ -1019,21 -893,19 +1019,21 @@@ const struct ref *transport_get_remote_
  {
        if (!transport->remote_refs)
                transport->remote_refs = transport->get_refs_list(transport, 0);
 +
        return transport->remote_refs;
  }
  
 -int transport_fetch_refs(struct transport *transport, const struct ref *refs)
 +int transport_fetch_refs(struct transport *transport, struct ref *refs)
  {
        int rc;
        int nr_heads = 0, nr_alloc = 0, nr_refs = 0;
 -      const struct ref **heads = NULL;
 -      const struct ref *rm;
 +      struct ref **heads = NULL;
 +      struct ref *rm;
  
        for (rm = refs; rm; rm = rm->next) {
                nr_refs++;
                if (rm->peer_ref &&
 +                  !is_null_sha1(rm->old_sha1) &&
                    !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1))
                        continue;
                ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
        }
  
        rc = transport->fetch(transport, nr_heads, heads);
 +
        free(heads);
        return rc;
  }
@@@ -1068,15 -939,6 +1068,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;
diff --combined transport.h
@@@ -4,15 -4,6 +4,15 @@@
  #include "cache.h"
  #include "remote.h"
  
 +struct git_transport_options {
 +      unsigned thin : 1;
 +      unsigned keep : 1;
 +      unsigned followtags : 1;
 +      int depth;
 +      const char *uploadpack;
 +      const char *receivepack;
 +};
 +
  struct transport {
        struct remote *remote;
        const char *url;
        int (*set_option)(struct transport *connection, const char *name,
                          const char *value);
  
 +      /**
 +       * Returns a list of the remote side's refs. In order to allow
 +       * the transport to try to share connections, for_push is a
 +       * hint as to whether the ultimate operation is a push or a fetch.
 +       *
 +       * If the transport is able to determine the remote hash for
 +       * the ref without a huge amount of effort, it should store it
 +       * in the ref's old_sha1 field; otherwise it should be all 0.
 +       **/
        struct ref *(*get_refs_list)(struct transport *transport, int for_push);
 -      int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs);
 +
 +      /**
 +       * Fetch the objects for the given refs. Note that this gets
 +       * an array, and should ignore the list structure.
 +       *
 +       * If the transport did not get hashes for refs in
 +       * get_refs_list(), it should set the old_sha1 fields in the
 +       * provided refs now.
 +       **/
 +      int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
 +
 +      /**
 +       * Push the objects and refs. Send the necessary objects, and
 +       * then, for any refs where peer_ref is set and
 +       * peer_ref->new_sha1 is different from old_sha1, tell the
 +       * remote side to update each ref in the list from old_sha1 to
 +       * peer_ref->new_sha1.
 +       *
 +       * Where possible, set the status for each ref appropriately.
 +       *
 +       * The transport must modify new_sha1 in the ref to the new
 +       * value if the remote accepted the change. Note that this
 +       * could be a different value from peer_ref->new_sha1 if the
 +       * process involved generating new commits.
 +       **/
        int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
        int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
 +      int (*connect)(struct transport *connection, const char *name,
 +                     const char *executable, int fd[2]);
  
 +      /** get_refs_list(), fetch(), and push_refs() can keep
 +       * resources (such as a connection) reserved for futher
 +       * use. disconnect() releases these resources.
 +       **/
        int (*disconnect)(struct transport *connection);
        char *pack_lockfile;
        signed verbose : 3;
-       /* Force progress even if the output is not a tty */
+       /* Force progress even if stderr is not a tty */
        unsigned progress : 1;
 +      /*
 +       * If transport is at least potentially smart, this points to
 +       * git_transport_options structure to use in case transport
 +       * actually turns out to be smart.
 +       */
 +      struct git_transport_options *smart_options;
  };
  
  #define TRANSPORT_PUSH_ALL 1
@@@ -128,15 -74,10 +128,15 @@@ int transport_push(struct transport *co
  
  const struct ref *transport_get_remote_refs(struct transport *transport);
  
 -int transport_fetch_refs(struct transport *transport, const struct ref *refs);
 +int transport_fetch_refs(struct transport *transport, struct ref *refs);
  void transport_unlock_pack(struct transport *transport);
  int transport_disconnect(struct transport *transport);
  char *transport_anonymize_url(const char *url);
 +void transport_take_over(struct transport *transport,
 +                       struct child_process *child);
 +
 +int transport_connect(struct transport *transport, const char *name,
 +                    const char *exec, int fd[2]);
  
  /* Transport methods defined outside transport.c */
  int transport_helper_init(struct transport *transport, const char *name);