reset: allow reset on unborn branch
[gitweb.git] / connect.c
index a6d7b137591e71738f8ba63760abf68419a4b100..49e56ba35a645359b0d7c1f7bbc9e2108b3424d9 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -5,6 +5,7 @@
 #include "refs.h"
 #include "run-command.h"
 #include "remote.h"
+#include "url.h"
 
 static char *server_capabilities;
 
@@ -21,7 +22,7 @@ static int check_ref(const char *name, int len, unsigned int flags)
        len -= 5;
 
        /* REF_NORMAL means that we don't want the magic fake tag refs */
-       if ((flags & REF_NORMAL) && check_ref_format(name) < 0)
+       if ((flags & REF_NORMAL) && check_refname_format(name, 0))
                return 0;
 
        /* REF_HEADS means that we want regular branch heads */
@@ -36,13 +37,37 @@ static int check_ref(const char *name, int len, unsigned int flags)
        return !(flags & ~REF_NORMAL);
 }
 
+int check_ref_type(const struct ref *ref, int flags)
+{
+       return check_ref(ref->name, strlen(ref->name), flags);
+}
+
+static void add_extra_have(struct extra_have_objects *extra, unsigned char *sha1)
+{
+       ALLOC_GROW(extra->array, extra->nr + 1, extra->alloc);
+       hashcpy(&(extra->array[extra->nr][0]), sha1);
+       extra->nr++;
+}
+
+static void die_initial_contact(int got_at_least_one_head)
+{
+       if (got_at_least_one_head)
+               die("The remote end hung up upon initial contact");
+       else
+               die("Could not read from remote repository.\n\n"
+                   "Please make sure you have the correct access rights\n"
+                   "and the repository exists.");
+}
+
 /*
  * Read all the refs from the other end
  */
 struct ref **get_remote_heads(int in, struct ref **list,
-                             int nr_match, char **match,
-                             unsigned int flags)
+                             unsigned int flags,
+                             struct extra_have_objects *extra_have)
 {
+       int got_at_least_one_head = 0;
+
        *list = NULL;
        for (;;) {
                struct ref *ref;
@@ -51,88 +76,101 @@ struct ref **get_remote_heads(int in, struct ref **list,
                char *name;
                int len, name_len;
 
-               len = packet_read_line(in, buffer, sizeof(buffer));
+               len = packet_read(in, buffer, sizeof(buffer));
+               if (len < 0)
+                       die_initial_contact(got_at_least_one_head);
+
                if (!len)
                        break;
                if (buffer[len-1] == '\n')
                        buffer[--len] = 0;
 
+               if (len > 4 && !prefixcmp(buffer, "ERR "))
+                       die("remote error: %s", buffer + 4);
+
                if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
                        die("protocol error: expected sha/ref, got '%s'", buffer);
                name = buffer + 41;
 
                name_len = strlen(name);
                if (len != name_len + 41) {
-                       if (server_capabilities)
-                               free(server_capabilities);
+                       free(server_capabilities);
                        server_capabilities = xstrdup(name + name_len + 1);
                }
 
-               if (!check_ref(name, name_len, flags))
+               if (extra_have &&
+                   name_len == 5 && !memcmp(".have", name, 5)) {
+                       add_extra_have(extra_have, old_sha1);
                        continue;
-               if (nr_match && !path_match(name, nr_match, match))
+               }
+
+               if (!check_ref(name, name_len, flags))
                        continue;
-               ref = alloc_ref(name_len + 1);
+               ref = alloc_ref(buffer + 41);
                hashcpy(ref->old_sha1, old_sha1);
-               memcpy(ref->name, buffer + 41, name_len + 1);
                *list = ref;
                list = &ref->next;
+               got_at_least_one_head = 1;
        }
        return list;
 }
 
-int server_supports(const char *feature)
+const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
 {
-       return server_capabilities &&
-               strstr(server_capabilities, feature) != NULL;
-}
+       int len;
 
-int get_ack(int fd, unsigned char *result_sha1)
-{
-       static char line[1000];
-       int len = packet_read_line(fd, line, sizeof(line));
-
-       if (!len)
-               die("git-fetch-pack: expected ACK/NAK, got EOF");
-       if (line[len-1] == '\n')
-               line[--len] = 0;
-       if (!strcmp(line, "NAK"))
-               return 0;
-       if (!prefixcmp(line, "ACK ")) {
-               if (!get_sha1_hex(line+4, result_sha1)) {
-                       if (strstr(line+45, "continue"))
-                               return 2;
-                       return 1;
+       if (!feature_list)
+               return NULL;
+
+       len = strlen(feature);
+       while (*feature_list) {
+               const char *found = strstr(feature_list, feature);
+               if (!found)
+                       return NULL;
+               if (feature_list == found || isspace(found[-1])) {
+                       const char *value = found + len;
+                       /* feature with no value (e.g., "thin-pack") */
+                       if (!*value || isspace(*value)) {
+                               if (lenp)
+                                       *lenp = 0;
+                               return value;
+                       }
+                       /* feature with a value (e.g., "agent=git/1.2.3") */
+                       else if (*value == '=') {
+                               value++;
+                               if (lenp)
+                                       *lenp = strcspn(value, " \t\n");
+                               return value;
+                       }
+                       /*
+                        * otherwise we matched a substring of another feature;
+                        * keep looking
+                        */
                }
+               feature_list = found + 1;
        }
-       die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
+       return NULL;
 }
 
-int path_match(const char *path, int nr, char **match)
+int parse_feature_request(const char *feature_list, const char *feature)
 {
-       int i;
-       int pathlen = strlen(path);
+       return !!parse_feature_value(feature_list, feature, NULL);
+}
 
-       for (i = 0; i < nr; i++) {
-               char *s = match[i];
-               int len = strlen(s);
+const char *server_feature_value(const char *feature, int *len)
+{
+       return parse_feature_value(server_capabilities, feature, len);
+}
 
-               if (!len || len > pathlen)
-                       continue;
-               if (memcmp(path + pathlen - len, s, len))
-                       continue;
-               if (pathlen > len && path[pathlen - len - 1] != '/')
-                       continue;
-               *s = 0;
-               return (i + 1);
-       }
-       return 0;
+int server_supports(const char *feature)
+{
+       return !!server_feature_value(feature, NULL);
 }
 
 enum protocol {
        PROTO_LOCAL = 1,
        PROTO_SSH,
-       PROTO_GIT,
+       PROTO_GIT
 };
 
 static enum protocol get_protocol(const char *name)
@@ -153,22 +191,46 @@ static enum protocol get_protocol(const char *name)
 #define STR_(s)        # s
 #define STR(s) STR_(s)
 
+static void get_host_and_port(char **host, const char **port)
+{
+       char *colon, *end;
+
+       if (*host[0] == '[') {
+               end = strchr(*host + 1, ']');
+               if (end) {
+                       *end = 0;
+                       end++;
+                       (*host)++;
+               } else
+                       end = *host;
+       } else
+               end = *host;
+       colon = strchr(end, ':');
+
+       if (colon) {
+               *colon = 0;
+               *port = colon + 1;
+       }
+}
+
+static void enable_keepalive(int sockfd)
+{
+       int ka = 1;
+
+       if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0)
+               fprintf(stderr, "unable to set SO_KEEPALIVE on socket: %s\n",
+                       strerror(errno));
+}
+
 #ifndef NO_IPV6
 
 static const char *ai_name(const struct addrinfo *ai)
 {
-       static char addr[INET_ADDRSTRLEN];
-       if ( AF_INET == ai->ai_family ) {
-               struct sockaddr_in *in;
-               in = (struct sockaddr_in *)ai->ai_addr;
-               inet_ntop(ai->ai_family, &in->sin_addr, addr, sizeof(addr));
-       } else if ( AF_INET6 == ai->ai_family ) {
-               struct sockaddr_in6 *in;
-               in = (struct sockaddr_in6 *)ai->ai_addr;
-               inet_ntop(ai->ai_family, &in->sin6_addr, addr, sizeof(addr));
-       } else {
+       static char addr[NI_MAXHOST];
+       if (getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), NULL, 0,
+                       NI_NUMERICHOST) != 0)
                strcpy(addr, "(unknown)");
-       }
+
        return addr;
 }
 
@@ -177,31 +239,16 @@ static const char *ai_name(const struct addrinfo *ai)
  */
 static int git_tcp_connect_sock(char *host, int flags)
 {
-       int sockfd = -1, saved_errno = 0;
-       char *colon, *end;
+       struct strbuf error_message = STRBUF_INIT;
+       int sockfd = -1;
        const char *port = STR(DEFAULT_GIT_PORT);
        struct addrinfo hints, *ai0, *ai;
        int gai;
        int cnt = 0;
 
-       if (host[0] == '[') {
-               end = strchr(host + 1, ']');
-               if (end) {
-                       *end = 0;
-                       end++;
-                       host++;
-               } else
-                       end = host;
-       } else
-               end = host;
-       colon = strchr(end, ':');
-
-       if (colon) {
-               *colon = 0;
-               port = colon + 1;
-               if (!*port)
-                       port = "<none>";
-       }
+       get_host_and_port(&host, &port);
+       if (!*port)
+               port = "<none>";
 
        memset(&hints, 0, sizeof(hints));
        hints.ai_socktype = SOCK_STREAM;
@@ -217,21 +264,15 @@ static int git_tcp_connect_sock(char *host, int flags)
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
 
-       for (ai0 = ai; ai; ai = ai->ai_next) {
+       for (ai0 = ai; ai; ai = ai->ai_next, cnt++) {
                sockfd = socket(ai->ai_family,
                                ai->ai_socktype, ai->ai_protocol);
-               if (sockfd < 0) {
-                       saved_errno = errno;
-                       continue;
-               }
-               if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
-                       saved_errno = errno;
-                       fprintf(stderr, "%s[%d: %s]: errno=%s\n",
-                               host,
-                               cnt,
-                               ai_name(ai),
-                               strerror(saved_errno));
-                       close(sockfd);
+               if ((sockfd < 0) ||
+                   (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0)) {
+                       strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n",
+                                   host, cnt, ai_name(ai), strerror(errno));
+                       if (0 <= sockfd)
+                               close(sockfd);
                        sockfd = -1;
                        continue;
                }
@@ -243,11 +284,15 @@ static int git_tcp_connect_sock(char *host, int flags)
        freeaddrinfo(ai0);
 
        if (sockfd < 0)
-               die("unable to connect a socket (%s)", strerror(saved_errno));
+               die("unable to connect to %s:\n%s", host, error_message.buf);
+
+       enable_keepalive(sockfd);
 
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\n");
 
+       strbuf_release(&error_message);
+
        return sockfd;
 }
 
@@ -258,31 +303,17 @@ static int git_tcp_connect_sock(char *host, int flags)
  */
 static int git_tcp_connect_sock(char *host, int flags)
 {
-       int sockfd = -1, saved_errno = 0;
-       char *colon, *end;
-       char *port = STR(DEFAULT_GIT_PORT), *ep;
+       struct strbuf error_message = STRBUF_INIT;
+       int sockfd = -1;
+       const char *port = STR(DEFAULT_GIT_PORT);
+       char *ep;
        struct hostent *he;
        struct sockaddr_in sa;
        char **ap;
        unsigned int nport;
        int cnt;
 
-       if (host[0] == '[') {
-               end = strchr(host + 1, ']');
-               if (end) {
-                       *end = 0;
-                       end++;
-                       host++;
-               } else
-                       end = host;
-       } else
-               end = host;
-       colon = strchr(end, ':');
-
-       if (colon) {
-               *colon = 0;
-               port = colon + 1;
-       }
+       get_host_and_port(&host, &port);
 
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "Looking up %s ... ", host);
@@ -295,7 +326,7 @@ static int git_tcp_connect_sock(char *host, int flags)
                /* Not numeric */
                struct servent *se = getservbyname(port,"tcp");
                if ( !se )
-                       die("Unknown port %s\n", port);
+                       die("Unknown port %s", port);
                nport = se->s_port;
        }
 
@@ -303,25 +334,21 @@ static int git_tcp_connect_sock(char *host, int flags)
                fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
 
        for (cnt = 0, ap = he->h_addr_list; *ap; ap++, cnt++) {
-               sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
-               if (sockfd < 0) {
-                       saved_errno = errno;
-                       continue;
-               }
-
                memset(&sa, 0, sizeof sa);
                sa.sin_family = he->h_addrtype;
                sa.sin_port = htons(nport);
                memcpy(&sa.sin_addr, *ap, he->h_length);
 
-               if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
-                       saved_errno = errno;
-                       fprintf(stderr, "%s[%d: %s]: errno=%s\n",
+               sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
+               if ((sockfd < 0) ||
+                   connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
+                       strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n",
                                host,
                                cnt,
                                inet_ntoa(*(struct in_addr *)&sa.sin_addr),
-                               strerror(saved_errno));
-                       close(sockfd);
+                               strerror(errno));
+                       if (0 <= sockfd)
+                               close(sockfd);
                        sockfd = -1;
                        continue;
                }
@@ -332,7 +359,9 @@ static int git_tcp_connect_sock(char *host, int flags)
        }
 
        if (sockfd < 0)
-               die("unable to connect a socket (%s)", strerror(saved_errno));
+               die("unable to connect to %s:\n%s", host, error_message.buf);
+
+       enable_keepalive(sockfd);
 
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\n");
@@ -353,18 +382,21 @@ static void git_tcp_connect(int fd[2], char *host, int flags)
 
 
 static char *git_proxy_command;
-static const char *rhost_name;
-static int rhost_len;
 
-static int git_proxy_command_options(const char *var, const char *value)
+static int git_proxy_command_options(const char *var, const char *value,
+               void *cb)
 {
        if (!strcmp(var, "core.gitproxy")) {
                const char *for_pos;
                int matchlen = -1;
                int hostlen;
+               const char *rhost_name = cb;
+               int rhost_len = strlen(rhost_name);
 
                if (git_proxy_command)
                        return 0;
+               if (!value)
+                       return config_error_nonbool(var);
                /* [core]
                 * ;# matches www.kernel.org as well
                 * gitproxy = netcatter-1 for kernel.org
@@ -398,67 +430,50 @@ static int git_proxy_command_options(const char *var, const char *value)
                return 0;
        }
 
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static int git_use_proxy(const char *host)
 {
-       rhost_name = host;
-       rhost_len = strlen(host);
        git_proxy_command = getenv("GIT_PROXY_COMMAND");
-       git_config(git_proxy_command_options);
-       rhost_name = NULL;
+       git_config(git_proxy_command_options, (void*)host);
        return (git_proxy_command && *git_proxy_command);
 }
 
-static void git_proxy_connect(int fd[2], char *host)
+static struct child_process *git_proxy_connect(int fd[2], char *host)
 {
        const char *port = STR(DEFAULT_GIT_PORT);
-       char *colon, *end;
-       const char *argv[4];
-       struct child_process proxy;
+       const char **argv;
+       struct child_process *proxy;
 
-       if (host[0] == '[') {
-               end = strchr(host + 1, ']');
-               if (end) {
-                       *end = 0;
-                       end++;
-                       host++;
-               } else
-                       end = host;
-       } else
-               end = host;
-       colon = strchr(end, ':');
-
-       if (colon) {
-               *colon = 0;
-               port = colon + 1;
-       }
+       get_host_and_port(&host, &port);
 
+       argv = xmalloc(sizeof(*argv) * 4);
        argv[0] = git_proxy_command;
        argv[1] = host;
        argv[2] = port;
        argv[3] = NULL;
-       memset(&proxy, 0, sizeof(proxy));
-       proxy.argv = argv;
-       proxy.in = -1;
-       proxy.out = -1;
-       if (start_command(&proxy))
+       proxy = xcalloc(1, sizeof(*proxy));
+       proxy->argv = argv;
+       proxy->in = -1;
+       proxy->out = -1;
+       if (start_command(proxy))
                die("cannot start proxy %s", argv[0]);
-       fd[0] = proxy.out; /* read from proxy stdout */
-       fd[1] = proxy.in;  /* write to proxy stdin */
+       fd[0] = proxy->out; /* read from proxy stdout */
+       fd[1] = proxy->in;  /* write to proxy stdin */
+       return proxy;
 }
 
 #define MAX_CMD_LEN 1024
 
-char *get_port(char *host)
+static char *get_port(char *host)
 {
        char *end;
        char *p = strchr(host, ':');
 
        if (p) {
-               strtol(p+1, &end, 10);
-               if (*end == '\0') {
+               long port = strtol(p + 1, &end, 10);
+               if (end != p + 1 && *end == '\0' && 0 <= port && port < 65536) {
                        *p = '\0';
                        return p+1;
                }
@@ -467,34 +482,45 @@ char *get_port(char *host)
        return NULL;
 }
 
+static struct child_process no_fork;
+
 /*
- * This returns NULL 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).
+ * 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.
+ * 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], char *url,
+struct child_process *git_connect(int fd[2], const char *url_orig,
                                  const char *prog, int flags)
 {
-       char *host, *path = url;
+       char *url;
+       char *host, *path;
        char *end;
        int c;
-       int pipefd[2][2];
-       struct child_process *conn;
+       struct child_process *conn = &no_fork;
        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);
+       else
+               url = xstrdup(url_orig);
+
        host = strstr(url, "://");
-       if(host) {
+       if (host) {
                *host = '\0';
                protocol = get_protocol(url);
                host += 3;
@@ -504,19 +530,25 @@ struct child_process *git_connect(int fd[2], char *url,
                c = ':';
        }
 
+       /*
+        * Don't do destructive transforms with git:// as that
+        * protocol code does '[]' unwrapping of its own.
+        */
        if (host[0] == '[') {
                end = strchr(host + 1, ']');
                if (end) {
-                       *end = 0;
+                       if (protocol != PROTO_GIT) {
+                               *end = 0;
+                               host++;
+                       }
                        end++;
-                       host++;
                } else
                        end = host;
        } else
                end = host;
 
        path = strchr(end, c);
-       if (path) {
+       if (path && !has_dos_drive_prefix(end)) {
                if (c == ':') {
                        protocol = PROTO_SSH;
                        *path++ = '\0';
@@ -547,7 +579,7 @@ struct child_process *git_connect(int fd[2], char *url,
         * Add support for ssh port: ssh://host.xy:<port>/...
         */
        if (protocol == PROTO_SSH && host != url)
-               port = get_port(host);
+               port = get_port(end);
 
        if (protocol == PROTO_GIT) {
                /* These underlying connection commands die() if they
@@ -555,90 +587,86 @@ struct child_process *git_connect(int fd[2], char *url,
                 */
                char *target_host = xstrdup(host);
                if (git_use_proxy(host))
-                       git_proxy_connect(fd, host);
+                       conn = git_proxy_connect(fd, host);
                else
                        git_tcp_connect(fd, host, flags);
                /*
                 * Separate original protocol components prog and path
-                * from extended components with a NUL byte.
+                * from extended host header with a NUL byte.
+                *
+                * Note: Do not add any other headers here!  Doing so
+                * will cause older git-daemon servers to crash.
                 */
                packet_write(fd[1],
                             "%s %s%chost=%s%c",
                             prog, path, 0,
                             target_host, 0);
                free(target_host);
+               free(url);
                if (free_path)
                        free(path);
-               return NULL;
+               return conn;
        }
 
-       if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
-               die("unable to create pipe pair for communication");
        conn = xcalloc(1, sizeof(*conn));
-       conn->pid = fork();
-       if (conn->pid < 0)
-               die("unable to fork");
-       if (!conn->pid) {
-               struct strbuf cmd;
-
-               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");
-
-               dup2(pipefd[1][0], 0);
-               dup2(pipefd[0][1], 1);
-               close(pipefd[0][0]);
-               close(pipefd[0][1]);
-               close(pipefd[1][0]);
-               close(pipefd[1][1]);
-               if (protocol == PROTO_SSH) {
-                       const char *ssh, *ssh_basename;
-                       ssh = getenv("GIT_SSH");
-                       if (!ssh) ssh = "ssh";
-                       ssh_basename = strrchr(ssh, '/');
-                       if (!ssh_basename)
-                               ssh_basename = ssh;
-                       else
-                               ssh_basename++;
 
-                       if (!port)
-                               execlp(ssh, ssh_basename, host, cmd.buf, NULL);
-                       else
-                               execlp(ssh, ssh_basename, "-p", port, host,
-                                      cmd.buf, NULL);
-               }
-               else {
-                       unsetenv(ALTERNATE_DB_ENVIRONMENT);
-                       unsetenv(DB_ENVIRONMENT);
-                       unsetenv(GIT_DIR_ENVIRONMENT);
-                       unsetenv(GIT_WORK_TREE_ENVIRONMENT);
-                       unsetenv(GRAFT_ENVIRONMENT);
-                       unsetenv(INDEX_ENVIRONMENT);
-                       execlp("sh", "sh", "-c", cmd.buf, NULL);
+       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;
                }
-               die("exec failed");
+               *arg++ = host;
        }
-       fd[0] = pipefd[0][0];
-       fd[1] = pipefd[1][1];
-       close(pipefd[0][1]);
-       close(pipefd[1][0]);
+       else {
+               /* remove repo-local variables from the environment */
+               conn->env = local_repo_env;
+               conn->use_shell = 1;
+       }
+       *arg++ = cmd.buf;
+       *arg = NULL;
+
+       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);
        return conn;
 }
 
+int git_connection_is_socket(struct child_process *conn)
+{
+       return conn == &no_fork;
+}
+
 int finish_connect(struct child_process *conn)
 {
-       if (!conn)
+       int code;
+       if (!conn || git_connection_is_socket(conn))
                return 0;
 
-       while (waitpid(conn->pid, NULL, 0) < 0) {
-               if (errno != EINTR)
-                       return -1;
-       }
+       code = finish_command(conn);
+       free(conn->argv);
        free(conn);
-       return 0;
+       return code;
 }