#include "git-compat-util.h" #include "cache.h" #include "config.h" #include "pkt-line.h" #include "quote.h" #include "refs.h" #include "run-command.h" #include "remote.h" #include "connect.h" #include "url.h" #include "string-list.h" #include "sha1-array.h" #include "transport.h" #include "strbuf.h" #include "protocol.h" static char *server_capabilities; static const char *parse_feature_value(const char *, const char *, int *); static int check_ref(const char *name, unsigned int flags) { if (!flags) return 1; if (!skip_prefix(name, "refs/", &name)) return 0; /* REF_NORMAL means that we don't want the magic fake tag refs */ if ((flags & REF_NORMAL) && check_refname_format(name, 0)) return 0; /* REF_HEADS means that we want regular branch heads */ if ((flags & REF_HEADS) && starts_with(name, "heads/")) return 1; /* REF_TAGS means that we want tags */ if ((flags & REF_TAGS) && starts_with(name, "tags/")) return 1; /* All type bits clear means that we are ok with anything */ return !(flags & ~REF_NORMAL); } int check_ref_type(const struct ref *ref, int flags) { return check_ref(ref->name, flags); } static void die_initial_contact(int unexpected) { if (unexpected) 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.")); } static void parse_one_symref_info(struct string_list *symref, const char *val, int len) { char *sym, *target; struct string_list_item *item; if (!len) return; /* just "symref" */ /* e.g. "symref=HEAD:refs/heads/master" */ sym = xmemdupz(val, len); target = strchr(sym, ':'); if (!target) /* just "symref=something" */ goto reject; *(target++) = '\0'; if (check_refname_format(sym, REFNAME_ALLOW_ONELEVEL) || check_refname_format(target, REFNAME_ALLOW_ONELEVEL)) /* "symref=bogus:pair */ goto reject; item = string_list_append_nodup(symref, sym); item->util = target; return; reject: free(sym); return; } static void annotate_refs_with_symref_info(struct ref *ref) { struct string_list symref = STRING_LIST_INIT_DUP; const char *feature_list = server_capabilities; while (feature_list) { int len; const char *val; val = parse_feature_value(feature_list, "symref", &len); if (!val) break; parse_one_symref_info(&symref, val, len); feature_list = val + 1; } string_list_sort(&symref); for (; ref; ref = ref->next) { struct string_list_item *item; item = string_list_lookup(&symref, ref->name); if (!item) continue; ref->symref = xstrdup((char *)item->util); } string_list_clear(&symref, 0); } /* * Read one line of a server's ref advertisement into packet_buffer. */ static int read_remote_ref(int in, char **src_buf, size_t *src_len, int *responded) { int len = packet_read(in, src_buf, src_len, packet_buffer, sizeof(packet_buffer), PACKET_READ_GENTLE_ON_EOF | PACKET_READ_CHOMP_NEWLINE); const char *arg; if (len < 0) die_initial_contact(*responded); if (len > 4 && skip_prefix(packet_buffer, "ERR ", &arg)) die("remote error: %s", arg); *responded = 1; return len; } #define EXPECTING_PROTOCOL_VERSION 0 #define EXPECTING_FIRST_REF 1 #define EXPECTING_REF 2 #define EXPECTING_SHALLOW 3 /* Returns 1 if packet_buffer is a protocol version pkt-line, 0 otherwise. */ static int process_protocol_version(void) { switch (determine_protocol_version_client(packet_buffer)) { case protocol_v1: return 1; case protocol_v0: return 0; default: die("server is speaking an unknown protocol"); } } static void process_capabilities(int *len) { int nul_location = strlen(packet_buffer); if (nul_location == *len) return; server_capabilities = xstrdup(packet_buffer + nul_location + 1); *len = nul_location; } static int process_dummy_ref(void) { struct object_id oid; const char *name; if (parse_oid_hex(packet_buffer, &oid, &name)) return 0; if (*name != ' ') return 0; name++; return !oidcmp(&null_oid, &oid) && !strcmp(name, "capabilities^{}"); } static void check_no_capabilities(int len) { if (strlen(packet_buffer) != len) warning("Ignoring capabilities after first line '%s'", packet_buffer + strlen(packet_buffer)); } static int process_ref(int len, struct ref ***list, unsigned int flags, struct oid_array *extra_have) { struct object_id old_oid; const char *name; if (parse_oid_hex(packet_buffer, &old_oid, &name)) return 0; if (*name != ' ') return 0; name++; if (extra_have && !strcmp(name, ".have")) { oid_array_append(extra_have, &old_oid); } else if (!strcmp(name, "capabilities^{}")) { die("protocol error: unexpected capabilities^{}"); } else if (check_ref(name, flags)) { struct ref *ref = alloc_ref(name); oidcpy(&ref->old_oid, &old_oid); **list = ref; *list = &ref->next; } check_no_capabilities(len); return 1; } static int process_shallow(int len, struct oid_array *shallow_points) { const char *arg; struct object_id old_oid; if (!skip_prefix(packet_buffer, "shallow ", &arg)) return 0; if (get_oid_hex(arg, &old_oid)) die("protocol error: expected shallow sha-1, got '%s'", arg); if (!shallow_points) die("repository on the other end cannot be shallow"); oid_array_append(shallow_points, &old_oid); check_no_capabilities(len); return 1; } /* * Read all the refs from the other end */ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, struct ref **list, unsigned int flags, struct oid_array *extra_have, struct oid_array *shallow_points) { struct ref **orig_list = list; /* * A hang-up after seeing some response from the other end * means that it is unexpected, as we know the other end is * willing to talk to us. A hang-up before seeing any * response does not necessarily mean an ACL problem, though. */ int responded = 0; int len; int state = EXPECTING_PROTOCOL_VERSION; *list = NULL; while ((len = read_remote_ref(in, &src_buf, &src_len, &responded))) { switch (state) { case EXPECTING_PROTOCOL_VERSION: if (process_protocol_version()) { state = EXPECTING_FIRST_REF; break; } state = EXPECTING_FIRST_REF; /* fallthrough */ case EXPECTING_FIRST_REF: process_capabilities(&len); if (process_dummy_ref()) { state = EXPECTING_SHALLOW; break; } state = EXPECTING_REF; /* fallthrough */ case EXPECTING_REF: if (process_ref(len, &list, flags, extra_have)) break; state = EXPECTING_SHALLOW; /* fallthrough */ case EXPECTING_SHALLOW: if (process_shallow(len, shallow_points)) break; die("protocol error: unexpected '%s'", packet_buffer); default: die("unexpected state %d", state); } } annotate_refs_with_symref_info(*orig_list); return list; } static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp) { int len; 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; } return NULL; } int parse_feature_request(const char *feature_list, const char *feature) { return !!parse_feature_value(feature_list, feature, NULL); } const char *server_feature_value(const char *feature, int *len) { return parse_feature_value(server_capabilities, feature, len); } int server_supports(const char *feature) { return !!server_feature_value(feature, NULL); } 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 "unknown protocol"; } } static enum protocol get_protocol(const char *name) { if (!strcmp(name, "ssh")) return PROTO_SSH; if (!strcmp(name, "git")) return PROTO_GIT; if (!strcmp(name, "git+ssh")) /* deprecated - do not use */ return PROTO_SSH; if (!strcmp(name, "ssh+git")) /* deprecated - do not use */ return PROTO_SSH; if (!strcmp(name, "file")) return PROTO_FILE; die("I don't handle protocol '%s'", name); } static char *host_end(char **hoststart, int removebrackets) { char *host = *hoststart; char *end; char *start = strstr(host, "@["); if (start) start++; /* Jump over '@' */ else start = host; if (start[0] == '[') { end = strchr(start + 1, ']'); if (end) { if (removebrackets) { *end = 0; memmove(start, start + 1, end - start); end++; } } else end = host; } else end = host; return end; } #define STR_(s) # s #define STR(s) STR_(s) static void get_host_and_port(char **host, const char **port) { char *colon, *end; end = host_end(host, 1); colon = strchr(end, ':'); if (colon) { long portnr = strtol(colon + 1, &end, 10); if (end != colon + 1 && *end == '\0' && 0 <= portnr && portnr < 65536) { *colon = 0; *port = colon + 1; } else if (!colon[1]) { *colon = 0; } } } 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[NI_MAXHOST]; if (getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), NULL, 0, NI_NUMERICHOST) != 0) xsnprintf(addr, sizeof(addr), "(unknown)"); return addr; } /* * Returns a connected socket() fd, or else die()s. */ static int git_tcp_connect_sock(char *host, int flags) { 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; get_host_and_port(&host, &port); if (!*port) port = ""; memset(&hints, 0, sizeof(hints)); if (flags & CONNECT_IPV4) hints.ai_family = AF_INET; else if (flags & CONNECT_IPV6) hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; if (flags & CONNECT_VERBOSE) fprintf(stderr, "Looking up %s ... ", host); gai = getaddrinfo(host, port, &hints, &ai); if (gai) die("Unable to look up %s (port %s) (%s)", host, port, gai_strerror(gai)); if (flags & CONNECT_VERBOSE) fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port); for (ai0 = ai; ai; ai = ai->ai_next, cnt++) { sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 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; } if (flags & CONNECT_VERBOSE) fprintf(stderr, "%s ", ai_name(ai)); break; } freeaddrinfo(ai0); if (sockfd < 0) 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; } #else /* NO_IPV6 */ /* * Returns a connected socket() fd, or else die()s. */ static int git_tcp_connect_sock(char *host, int flags) { 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; get_host_and_port(&host, &port); if (flags & CONNECT_VERBOSE) fprintf(stderr, "Looking up %s ... ", host); he = gethostbyname(host); if (!he) die("Unable to look up %s (%s)", host, hstrerror(h_errno)); nport = strtoul(port, &ep, 10); if ( ep == port || *ep ) { /* Not numeric */ struct servent *se = getservbyname(port,"tcp"); if ( !se ) die("Unknown port %s", port); nport = se->s_port; } if (flags & CONNECT_VERBOSE) fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port); for (cnt = 0, ap = he->h_addr_list; *ap; ap++, cnt++) { memset(&sa, 0, sizeof sa); sa.sin_family = he->h_addrtype; sa.sin_port = htons(nport); memcpy(&sa.sin_addr, *ap, he->h_length); 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(errno)); if (0 <= sockfd) close(sockfd); sockfd = -1; continue; } if (flags & CONNECT_VERBOSE) fprintf(stderr, "%s ", inet_ntoa(*(struct in_addr *)&sa.sin_addr)); break; } if (sockfd < 0) die("unable to connect to %s:\n%s", host, error_message.buf); enable_keepalive(sockfd); if (flags & CONNECT_VERBOSE) fprintf(stderr, "done.\n"); return sockfd; } #endif /* NO_IPV6 */ static void git_tcp_connect(int fd[2], char *host, int flags) { int sockfd = git_tcp_connect_sock(host, flags); fd[0] = sockfd; fd[1] = dup(sockfd); } static char *git_proxy_command; 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 * gitproxy = netcatter-2 for sample.xz * gitproxy = netcatter-default */ for_pos = strstr(value, " for "); if (!for_pos) /* matches everybody */ matchlen = strlen(value); else { hostlen = strlen(for_pos + 5); if (rhost_len < hostlen) matchlen = -1; else if (!strncmp(for_pos + 5, rhost_name + rhost_len - hostlen, hostlen) && ((rhost_len == hostlen) || rhost_name[rhost_len - hostlen -1] == '.')) matchlen = for_pos - value; else matchlen = -1; } if (0 <= matchlen) { /* core.gitproxy = none for kernel.org */ if (matchlen == 4 && !memcmp(value, "none", 4)) matchlen = 0; git_proxy_command = xmemdupz(value, matchlen); } return 0; } return git_default_config(var, value, cb); } static int git_use_proxy(const char *host) { git_proxy_command = getenv("GIT_PROXY_COMMAND"); git_config(git_proxy_command_options, (void*)host); return (git_proxy_command && *git_proxy_command); } static struct child_process *git_proxy_connect(int fd[2], char *host) { const char *port = STR(DEFAULT_GIT_PORT); struct child_process *proxy; get_host_and_port(&host, &port); if (looks_like_command_line_option(host)) die("strange hostname '%s' blocked", host); if (looks_like_command_line_option(port)) die("strange port '%s' blocked", port); proxy = xmalloc(sizeof(*proxy)); child_process_init(proxy); argv_array_push(&proxy->args, git_proxy_command); argv_array_push(&proxy->args, host); argv_array_push(&proxy->args, port); proxy->in = -1; proxy->out = -1; if (start_command(proxy)) die("cannot start proxy %s", git_proxy_command); fd[0] = proxy->out; /* read from proxy stdout */ fd[1] = proxy->in; /* write to proxy stdin */ return proxy; } static char *get_port(char *host) { 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 NULL; } /* * Extract protocol and relevant parts from the specified connection URL. * The caller must free() the returned strings. */ static enum protocol parse_connect_url(const char *url_orig, char **ret_host, char **ret_path) { char *url; char *host, *path; char *end; int separator = '/'; enum protocol protocol = PROTO_LOCAL; if (is_url(url_orig)) url = url_decode(url_orig); else url = xstrdup(url_orig); host = strstr(url, "://"); if (host) { *host = '\0'; protocol = get_protocol(url); host += 3; } else { host = url; if (!url_is_local_not_ssh(url)) { protocol = PROTO_SSH; separator = ':'; } } /* * Don't do destructive transforms as protocol code does * '[]' unwrapping in get_host_and_port() */ end = host_end(&host, 0); 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 */ 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++; } path = xstrdup(path); *end = '\0'; *ret_host = xstrdup(host); *ret_path = path; free(url); return protocol; } static struct child_process no_fork = CHILD_PROCESS_INIT; static const char *get_ssh_command(void) { const char *ssh; if ((ssh = getenv("GIT_SSH_COMMAND"))) return ssh; if (!git_config_get_string_const("core.sshcommand", &ssh)) return ssh; return NULL; } enum ssh_variant { VARIANT_SIMPLE, VARIANT_SSH, VARIANT_PLINK, VARIANT_PUTTY, VARIANT_TORTOISEPLINK, }; static int override_ssh_variant(enum ssh_variant *ssh_variant) { const char *variant = getenv("GIT_SSH_VARIANT"); if (!variant && git_config_get_string_const("ssh.variant", &variant)) return 0; if (!strcmp(variant, "plink")) *ssh_variant = VARIANT_PLINK; else if (!strcmp(variant, "putty")) *ssh_variant = VARIANT_PUTTY; else if (!strcmp(variant, "tortoiseplink")) *ssh_variant = VARIANT_TORTOISEPLINK; else if (!strcmp(variant, "simple")) *ssh_variant = VARIANT_SIMPLE; else *ssh_variant = VARIANT_SSH; return 1; } static enum ssh_variant determine_ssh_variant(const char *ssh_command, int is_cmdline) { enum ssh_variant ssh_variant = VARIANT_SIMPLE; const char *variant; char *p = NULL; if (override_ssh_variant(&ssh_variant)) return ssh_variant; if (!is_cmdline) { p = xstrdup(ssh_command); variant = basename(p); } else { const char **ssh_argv; p = xstrdup(ssh_command); if (split_cmdline(p, &ssh_argv) > 0) { variant = basename((char *)ssh_argv[0]); /* * At this point, variant points into the buffer * referenced by p, hence we do not need ssh_argv * any longer. */ free(ssh_argv); } else { free(p); return ssh_variant; } } if (!strcasecmp(variant, "ssh") || !strcasecmp(variant, "ssh.exe")) ssh_variant = VARIANT_SSH; else if (!strcasecmp(variant, "plink") || !strcasecmp(variant, "plink.exe")) ssh_variant = VARIANT_PLINK; else if (!strcasecmp(variant, "tortoiseplink") || !strcasecmp(variant, "tortoiseplink.exe")) ssh_variant = VARIANT_TORTOISEPLINK; free(p); return ssh_variant; } /* * 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; /* Without this we cannot rely on waitpid() to tell * what happened to our children. */ signal(SIGCHLD, SIG_DFL); protocol = parse_connect_url(url, &hostandport, &path); if ((flags & CONNECT_DIAG_URL) && (protocol != PROTO_SSH)) { 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) { struct strbuf request = STRBUF_INIT; /* * Set up virtual host information based on where we will * connect, unless the user has overridden us in * the environment. */ char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST"); if (target_host) target_host = xstrdup(target_host); else target_host = xstrdup(hostandport); transport_check_allowed("git"); /* These underlying connection commands die() if they * cannot connect. */ if (git_use_proxy(hostandport)) conn = git_proxy_connect(fd, hostandport); else git_tcp_connect(fd, hostandport, flags); /* * Separate original protocol components prog and path * 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. */ strbuf_addf(&request, "%s %s%chost=%s%c", prog, path, 0, target_host, 0); /* If using a new version put that stuff here after a second null byte */ if (get_protocol_version_config() > 0) { strbuf_addch(&request, '\0'); strbuf_addf(&request, "version=%d%c", get_protocol_version_config(), '\0'); } packet_write(fd[1], request.buf, request.len); free(target_host); strbuf_release(&request); } else { struct strbuf cmd = STRBUF_INIT; const char *const *var; conn = xmalloc(sizeof(*conn)); child_process_init(conn); if (looks_like_command_line_option(path)) die("strange pathname '%s' blocked", path); strbuf_addstr(&cmd, prog); strbuf_addch(&cmd, ' '); sq_quote_buf(&cmd, path); /* remove repo-local variables from the environment */ for (var = local_repo_env; *var; var++) argv_array_push(&conn->env_array, *var); conn->use_shell = 1; conn->in = conn->out = -1; if (protocol == PROTO_SSH) { const char *ssh; enum ssh_variant variant; char *ssh_host = hostandport; const char *port = NULL; transport_check_allowed("ssh"); get_host_and_port(&ssh_host, &port); if (!port) port = get_port(ssh_host); if (flags & CONNECT_DIAG_URL) { printf("Diag: url=%s\n", url ? url : "NULL"); printf("Diag: protocol=%s\n", prot_name(protocol)); printf("Diag: userandhost=%s\n", ssh_host ? ssh_host : "NULL"); printf("Diag: port=%s\n", port ? port : "NONE"); printf("Diag: path=%s\n", path ? path : "NULL"); free(hostandport); free(path); free(conn); strbuf_release(&cmd); return NULL; } if (looks_like_command_line_option(ssh_host)) die("strange hostname '%s' blocked", ssh_host); ssh = get_ssh_command(); if (ssh) { variant = determine_ssh_variant(ssh, 1); } else { /* * GIT_SSH is the no-shell version of * GIT_SSH_COMMAND (and must remain so for * historical compatibility). */ conn->use_shell = 0; ssh = getenv("GIT_SSH"); if (!ssh) ssh = "ssh"; variant = determine_ssh_variant(ssh, 0); } argv_array_push(&conn->args, ssh); if (variant == VARIANT_SSH && get_protocol_version_config() > 0) { argv_array_push(&conn->args, "-o"); argv_array_push(&conn->args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT); argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d", get_protocol_version_config()); } if (variant != VARIANT_SIMPLE) { if (flags & CONNECT_IPV4) argv_array_push(&conn->args, "-4"); else if (flags & CONNECT_IPV6) argv_array_push(&conn->args, "-6"); } if (variant == VARIANT_TORTOISEPLINK) argv_array_push(&conn->args, "-batch"); if (port && variant != VARIANT_SIMPLE) { if (variant == VARIANT_SSH) argv_array_push(&conn->args, "-p"); else argv_array_push(&conn->args, "-P"); argv_array_push(&conn->args, port); } argv_array_push(&conn->args, ssh_host); } else { transport_check_allowed("file"); if (get_protocol_version_config() > 0) { argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d", get_protocol_version_config()); } } argv_array_push(&conn->args, cmd.buf); 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(hostandport); free(path); return conn; } int git_connection_is_socket(struct child_process *conn) { return conn == &no_fork; } int finish_connect(struct child_process *conn) { int code; if (!conn || git_connection_is_socket(conn)) return 0; code = finish_command(conn); free(conn); return code; }