Merge branch 'tb/clone-ssh-with-colon-for-port'
authorJunio C Hamano <gitster@pobox.com>
Tue, 17 Dec 2013 20:03:31 +0000 (12:03 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 17 Dec 2013 20:03:32 +0000 (12:03 -0800)
Be more careful when parsing remote repository URL given in the
scp-style host:path notation.

* tb/clone-ssh-with-colon-for-port:
git_connect(): use common return point
connect.c: refactor url parsing
git_connect(): refactor the port handling for ssh
git fetch: support host:/~repo
t5500: add test cases for diag-url
git fetch-pack: add --diag-url
git_connect: factor out discovery of the protocol and its parts
git_connect: remove artificial limit of a remote command
t5601: add tests for ssh
t5601: remove clear_ssh, refactor setup_ssh_wrapper

1  2 
builtin/fetch-pack.c
connect.c
transport.c
diff --combined builtin/fetch-pack.c
index 10fcdc2ff2a6a77ec6a4edf418b2ca46600b73bd,758b5acd55aadcb43b6d11b02f0555a214b35744..8b8978a25287bce6c4f937ff52df6cb7ae8e429e
@@@ -7,7 -7,7 +7,7 @@@
  static const char fetch_pack_usage[] =
  "git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
  "[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
- "[--no-progress] [-v] [<host>:]<directory> [<refs>...]";
+ "[--no-progress] [--diag-url] [-v] [<host>:]<directory> [<refs>...]";
  
  static void add_sought_entry_mem(struct ref ***sought, int *nr, int *alloc,
                                 const char *name, int namelen)
@@@ -48,11 -48,11 +48,11 @@@ int cmd_fetch_pack(int argc, const cha
        for (i = 1; i < argc && *argv[i] == '-'; i++) {
                const char *arg = argv[i];
  
 -              if (!prefixcmp(arg, "--upload-pack=")) {
 +              if (starts_with(arg, "--upload-pack=")) {
                        args.uploadpack = arg + 14;
                        continue;
                }
 -              if (!prefixcmp(arg, "--exec=")) {
 +              if (starts_with(arg, "--exec=")) {
                        args.uploadpack = arg + 7;
                        continue;
                }
                        args.stdin_refs = 1;
                        continue;
                }
+               if (!strcmp("--diag-url", arg)) {
+                       args.diag_url = 1;
+                       continue;
+               }
                if (!strcmp("-v", arg)) {
                        args.verbose = 1;
                        continue;
                }
 -              if (!prefixcmp(arg, "--depth=")) {
 +              if (starts_with(arg, "--depth=")) {
                        args.depth = strtol(arg + 8, NULL, 0);
                        continue;
                }
                fd[0] = 0;
                fd[1] = 1;
        } else {
+               int flags = args.verbose ? CONNECT_VERBOSE : 0;
+               if (args.diag_url)
+                       flags |= CONNECT_DIAG_URL;
                conn = git_connect(fd, dest, args.uploadpack,
-                                  args.verbose ? CONNECT_VERBOSE : 0);
+                                  flags);
+               if (!conn)
+                       return args.diag_url ? 0 : 1;
        }
        get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL);
  
        ref = fetch_pack(&args, fd, conn, ref, dest,
diff --combined connect.c
index d51d1066330f547e336038fad1c5d43c90e28171,8a013a723b4d81deb76baabb7615c06b0caa068c..c763eeda863d62093a9f37d281e8b1afc1a1e254
+++ b/connect.c
@@@ -145,7 -145,7 +145,7 @@@ struct ref **get_remote_heads(int in, c
                if (!len)
                        break;
  
 -              if (len > 4 && !prefixcmp(buffer, "ERR "))
 +              if (len > 4 && starts_with(buffer, "ERR "))
                        die("remote error: %s", buffer + 4);
  
                if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
@@@ -232,10 -232,34 +232,34 @@@ int server_supports(const char *feature
  
  enum protocol {
        PROTO_LOCAL = 1,
+       PROTO_FILE,
        PROTO_SSH,
        PROTO_GIT
  };
  
+ int url_is_local_not_ssh(const char *url)
+ {
+       const char *colon = strchr(url, ':');
+       const char *slash = strchr(url, '/');
+       return !colon || (slash && slash < colon) ||
+               has_dos_drive_prefix(url);
+ }
+ static const char *prot_name(enum protocol protocol)
+ {
+       switch (protocol) {
+               case PROTO_LOCAL:
+               case PROTO_FILE:
+                       return "file";
+               case PROTO_SSH:
+                       return "ssh";
+               case PROTO_GIT:
+                       return "git";
+               default:
+                       return "unkown protocol";
+       }
+ }
  static enum protocol get_protocol(const char *name)
  {
        if (!strcmp(name, "ssh"))
        if (!strcmp(name, "ssh+git"))
                return PROTO_SSH;
        if (!strcmp(name, "file"))
-               return PROTO_LOCAL;
+               return PROTO_FILE;
        die("I don't handle protocol '%s'", name);
  }
  
@@@ -527,55 -551,31 +551,31 @@@ static struct child_process *git_proxy_
        return proxy;
  }
  
- #define MAX_CMD_LEN 1024
- static char *get_port(char *host)
+ static const char *get_port_numeric(const char *p)
  {
        char *end;
-       char *p = strchr(host, ':');
        if (p) {
                long port = strtol(p + 1, &end, 10);
                if (end != p + 1 && *end == '\0' && 0 <= port && port < 65536) {
-                       *p = '\0';
-                       return p+1;
+                       return p;
                }
        }
  
        return NULL;
  }
  
- static struct child_process no_fork;
  /*
-  * This returns a dummy child_process if the transport protocol does not
-  * need fork(2), or a struct child_process object if it does.  Once done,
-  * finish the connection with finish_connect() with the value returned from
-  * this function (it is safe to call finish_connect() with NULL to support
-  * the former case).
-  *
-  * If it returns, the connect is successful; it just dies on errors (this
-  * will hopefully be changed in a libification effort, to return NULL when
-  * the connection failed).
+  * Extract protocol and relevant parts from the specified connection URL.
+  * The caller must free() the returned strings.
   */
- struct child_process *git_connect(int fd[2], const char *url_orig,
-                                 const char *prog, int flags)
+ static enum protocol parse_connect_url(const char *url_orig, char **ret_host,
+                                      char **ret_path)
  {
        char *url;
        char *host, *path;
        char *end;
-       int c;
-       struct child_process *conn = &no_fork;
+       int separator = '/';
        enum protocol protocol = PROTO_LOCAL;
-       int free_path = 0;
-       char *port = NULL;
-       const char **arg;
-       struct strbuf cmd;
-       /* Without this we cannot rely on waitpid() to tell
-        * what happened to our children.
-        */
-       signal(SIGCHLD, SIG_DFL);
  
        if (is_url(url_orig))
                url = url_decode(url_orig);
                *host = '\0';
                protocol = get_protocol(url);
                host += 3;
-               c = '/';
        } else {
                host = url;
-               c = ':';
+               if (!url_is_local_not_ssh(url)) {
+                       protocol = PROTO_SSH;
+                       separator = ':';
+               }
        }
  
        /*
-        * Don't do destructive transforms with git:// as that
-        * protocol code does '[]' unwrapping of its own.
+        * Don't do destructive transforms as protocol code does
+        * '[]' unwrapping in get_host_and_port()
         */
        if (host[0] == '[') {
                end = strchr(host + 1, ']');
                if (end) {
-                       if (protocol != PROTO_GIT) {
-                               *end = 0;
-                               host++;
-                       }
                        end++;
                } else
                        end = host;
        } else
                end = host;
  
-       path = strchr(end, c);
-       if (path && !has_dos_drive_prefix(end)) {
-               if (c == ':') {
-                       if (host != url || path < strchrnul(host, '/')) {
-                               protocol = PROTO_SSH;
-                               *path++ = '\0';
-                       } else /* '/' in the host part, assume local path */
-                               path = end;
-               }
-       } else
+       if (protocol == PROTO_LOCAL)
                path = end;
+       else if (protocol == PROTO_FILE && has_dos_drive_prefix(end))
+               path = end; /* "file://$(pwd)" may be "file://C:/projects/repo" */
+       else
+               path = strchr(end, separator);
  
        if (!path || !*path)
                die("No path specified. See 'man git-pull' for valid url syntax");
         * null-terminate hostname and point path to ~ for URL's like this:
         *    ssh://host.xz/~user/repo
         */
-       if (protocol != PROTO_LOCAL && host != url) {
-               char *ptr = path;
+       end = path; /* Need to \0 terminate host here */
+       if (separator == ':')
+               path++; /* path starts after ':' */
+       if (protocol == PROTO_GIT || protocol == PROTO_SSH) {
                if (path[1] == '~')
                        path++;
-               else {
-                       path = xstrdup(ptr);
-                       free_path = 1;
-               }
-               *ptr = '\0';
        }
  
-       /*
-        * Add support for ssh port: ssh://host.xy:<port>/...
+       path = xstrdup(path);
+       *end = '\0';
+       *ret_host = xstrdup(host);
+       *ret_path = path;
+       free(url);
+       return protocol;
+ }
+ static struct child_process no_fork;
+ /*
+  * This returns a dummy child_process if the transport protocol does not
+  * need fork(2), or a struct child_process object if it does.  Once done,
+  * finish the connection with finish_connect() with the value returned from
+  * this function (it is safe to call finish_connect() with NULL to support
+  * the former case).
+  *
+  * If it returns, the connect is successful; it just dies on errors (this
+  * will hopefully be changed in a libification effort, to return NULL when
+  * the connection failed).
+  */
+ struct child_process *git_connect(int fd[2], const char *url,
+                                 const char *prog, int flags)
+ {
+       char *hostandport, *path;
+       struct child_process *conn = &no_fork;
+       enum protocol protocol;
+       const char **arg;
+       struct strbuf cmd = STRBUF_INIT;
+       /* Without this we cannot rely on waitpid() to tell
+        * what happened to our children.
         */
-       if (protocol == PROTO_SSH && host != url)
-               port = get_port(end);
+       signal(SIGCHLD, SIG_DFL);
  
-       if (protocol == PROTO_GIT) {
+       protocol = parse_connect_url(url, &hostandport, &path);
+       if (flags & CONNECT_DIAG_URL) {
+               printf("Diag: url=%s\n", url ? url : "NULL");
+               printf("Diag: protocol=%s\n", prot_name(protocol));
+               printf("Diag: hostandport=%s\n", hostandport ? hostandport : "NULL");
+               printf("Diag: path=%s\n", path ? path : "NULL");
+               conn = NULL;
+       } else if (protocol == PROTO_GIT) {
                /* These underlying connection commands die() if they
                 * cannot connect.
                 */
-               char *target_host = xstrdup(host);
-               if (git_use_proxy(host))
-                       conn = git_proxy_connect(fd, host);
+               char *target_host = xstrdup(hostandport);
+               if (git_use_proxy(hostandport))
+                       conn = git_proxy_connect(fd, hostandport);
                else
-                       git_tcp_connect(fd, host, flags);
+                       git_tcp_connect(fd, hostandport, flags);
                /*
                 * Separate original protocol components prog and path
                 * from extended host header with a NUL byte.
                             prog, path, 0,
                             target_host, 0);
                free(target_host);
-               free(url);
-               if (free_path)
-                       free(path);
-               return conn;
-       }
-       conn = xcalloc(1, sizeof(*conn));
-       strbuf_init(&cmd, MAX_CMD_LEN);
-       strbuf_addstr(&cmd, prog);
-       strbuf_addch(&cmd, ' ');
-       sq_quote_buf(&cmd, path);
-       if (cmd.len >= MAX_CMD_LEN)
-               die("command line too long");
-       conn->in = conn->out = -1;
-       conn->argv = arg = xcalloc(7, sizeof(*arg));
-       if (protocol == PROTO_SSH) {
-               const char *ssh = getenv("GIT_SSH");
-               int putty = ssh && strcasestr(ssh, "plink");
-               if (!ssh) ssh = "ssh";
-               *arg++ = ssh;
-               if (putty && !strcasestr(ssh, "tortoiseplink"))
-                       *arg++ = "-batch";
-               if (port) {
-                       /* P is for PuTTY, p is for OpenSSH */
-                       *arg++ = putty ? "-P" : "-p";
-                       *arg++ = port;
+       } else {
+               conn = xcalloc(1, sizeof(*conn));
+               strbuf_addstr(&cmd, prog);
+               strbuf_addch(&cmd, ' ');
+               sq_quote_buf(&cmd, path);
+               conn->in = conn->out = -1;
+               conn->argv = arg = xcalloc(7, sizeof(*arg));
+               if (protocol == PROTO_SSH) {
+                       const char *ssh = getenv("GIT_SSH");
+                       int putty = ssh && strcasestr(ssh, "plink");
+                       char *ssh_host = hostandport;
+                       const char *port = NULL;
+                       get_host_and_port(&ssh_host, &port);
+                       port = get_port_numeric(port);
+                       if (!ssh) ssh = "ssh";
+                       *arg++ = ssh;
+                       if (putty && !strcasestr(ssh, "tortoiseplink"))
+                               *arg++ = "-batch";
+                       if (port) {
+                               /* P is for PuTTY, p is for OpenSSH */
+                               *arg++ = putty ? "-P" : "-p";
+                               *arg++ = port;
+                       }
+                       *arg++ = ssh_host;
+               }       else {
+                       /* remove repo-local variables from the environment */
+                       conn->env = local_repo_env;
+                       conn->use_shell = 1;
                }
-               *arg++ = host;
-       }
-       else {
-               /* remove repo-local variables from the environment */
-               conn->env = local_repo_env;
-               conn->use_shell = 1;
-       }
-       *arg++ = cmd.buf;
-       *arg = NULL;
+               *arg++ = cmd.buf;
+               *arg = NULL;
  
-       if (start_command(conn))
-               die("unable to fork");
+               if (start_command(conn))
+                       die("unable to fork");
  
-       fd[0] = conn->out; /* read from child's stdout */
-       fd[1] = conn->in;  /* write to child's stdin */
-       strbuf_release(&cmd);
-       free(url);
-       if (free_path)
-               free(path);
+               fd[0] = conn->out; /* read from child's stdout */
+               fd[1] = conn->in;  /* write to child's stdin */
+               strbuf_release(&cmd);
+       }
+       free(hostandport);
+       free(path);
        return conn;
  }
  
diff --combined transport.c
index 8023956696d0d00bbc4b65c148eade97906a2729,5485e2ab1e86fd7f02c2078ed6505cdec292ee4f..824c5b93f9e1c44887b17ef116b360ec377076a6
@@@ -169,13 -169,13 +169,13 @@@ static void set_upstreams(struct transp
                remotename = ref->name;
                tmp = resolve_ref_unsafe(localname, sha, 1, &flag);
                if (tmp && flag & REF_ISSYMREF &&
 -                      !prefixcmp(tmp, "refs/heads/"))
 +                      starts_with(tmp, "refs/heads/"))
                        localname = tmp;
  
                /* Both source and destination must be local branches. */
 -              if (!localname || prefixcmp(localname, "refs/heads/"))
 +              if (!localname || !starts_with(localname, "refs/heads/"))
                        continue;
 -              if (!remotename || prefixcmp(remotename, "refs/heads/"))
 +              if (!remotename || !starts_with(remotename, "refs/heads/"))
                        continue;
  
                if (!pretend)
  
  static const char *rsync_url(const char *url)
  {
 -      return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url;
 +      return !starts_with(url, "rsync://") ? skip_prefix(url, "rsync:") : url;
  }
  
  static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
@@@ -296,8 -296,8 +296,8 @@@ static int write_one_ref(const char *na
        FILE *f;
  
        /* when called via for_each_ref(), flags is non-zero */
 -      if (flags && prefixcmp(name, "refs/heads/") &&
 -                      prefixcmp(name, "refs/tags/"))
 +      if (flags && !starts_with(name, "refs/heads/") &&
 +                      !starts_with(name, "refs/tags/"))
                return 0;
  
        strbuf_addstr(buf, name);
@@@ -652,7 -652,7 +652,7 @@@ static void print_ok_ref_status(struct 
                print_ref_status('-', "[deleted]", ref, NULL, NULL, porcelain);
        else if (is_null_sha1(ref->old_sha1))
                print_ref_status('*',
 -                      (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
 +                      (starts_with(ref->name, "refs/tags/") ? "[new tag]" :
                        "[new branch]"),
                        ref, ref->peer_ref, NULL, porcelain);
        else {
@@@ -885,14 -885,6 +885,6 @@@ void transport_take_over(struct transpo
        transport->cannot_reuse = 1;
  }
  
- static int is_local(const char *url)
- {
-       const char *colon = strchr(url, ':');
-       const char *slash = strchr(url, '/');
-       return !colon || (slash && slash < colon) ||
-               has_dos_drive_prefix(url);
- }
  static int is_file(const char *url)
  {
        struct stat buf;
@@@ -930,18 -922,18 +922,18 @@@ struct transport *transport_get(struct 
  
                while (is_urlschemechar(p == url, *p))
                        p++;
 -              if (!prefixcmp(p, "::"))
 +              if (starts_with(p, "::"))
                        helper = xstrndup(url, p - url);
        }
  
        if (helper) {
                transport_helper_init(ret, helper);
 -      } else if (!prefixcmp(url, "rsync:")) {
 +      } else if (starts_with(url, "rsync:")) {
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
                ret->smart_options = NULL;
-       } else if (is_local(url) && is_file(url) && is_bundle(url, 1)) {
+       } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
                ret->disconnect = close_bundle;
                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://")) {
 +              || starts_with(url, "file://")
 +              || starts_with(url, "git://")
 +              || starts_with(url, "ssh://")
 +              || starts_with(url, "git+ssh://")
 +              || starts_with(url, "ssh+git://")) {
                /* These are builtin smart transports. */
                struct git_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
@@@ -1297,7 -1289,7 +1289,7 @@@ char *transport_anonymize_url(const cha
        size_t anon_len, prefix_len = 0;
  
        anon_part = strchr(url, '@');
-       if (is_local(url) || !anon_part)
+       if (url_is_local_not_ssh(url) || !anon_part)
                goto literal_copy;
  
        anon_len = strlen(++anon_part);