+#include "git-compat-util.h"
#include "cache.h"
#include "pkt-line.h"
#include "quote.h"
+#include "refs.h"
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
+static char *server_capabilities = NULL;
+
/*
* Read all the refs from the other end
*/
-struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match)
+struct ref **get_remote_heads(int in, struct ref **list,
+ int nr_match, char **match, int ignore_funny)
{
*list = NULL;
for (;;) {
unsigned char old_sha1[20];
static char buffer[1000];
char *name;
- int len;
+ int len, name_len;
len = packet_read_line(in, buffer, sizeof(buffer));
if (!len)
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);
+ server_capabilities = strdup(name + name_len + 1);
+ }
+
+ if (ignore_funny && 45 < len && !memcmp(name, "refs/", 5) &&
+ check_ref_format(name + 5))
+ continue;
+
if (nr_match && !path_match(name, nr_match, match))
continue;
ref = xcalloc(1, sizeof(*ref) + len - 40);
return list;
}
+int server_supports(const char *feature)
+{
+ return server_capabilities &&
+ strstr(server_capabilities, feature) != NULL;
+}
+
int get_ack(int fd, unsigned char *result_sha1)
{
static char line[1000];
line[--len] = 0;
if (!strcmp(line, "NAK"))
return 0;
- if (!strncmp(line, "ACK ", 3)) {
- if (!get_sha1_hex(line+4, result_sha1))
+ if (!strncmp(line, "ACK ", 4)) {
+ if (!get_sha1_hex(line+4, result_sha1)) {
+ if (strstr(line+45, "continue"))
+ return 2;
return 1;
+ }
}
die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
}
struct refspec {
char *src;
char *dst;
+ char force;
};
+/*
+ * A:B means fast forward remote B with local A.
+ * +A:B means overwrite remote B with local A.
+ * +A is a shorthand for +A:A.
+ * A is a shorthand for A:A.
+ */
static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
{
int i;
- struct refspec *rs = xmalloc(sizeof(*rs) * (nr_refspec + 1));
+ struct refspec *rs = xcalloc(sizeof(*rs), (nr_refspec + 1));
for (i = 0; i < nr_refspec; i++) {
char *sp, *dp, *ep;
sp = refspec[i];
+ if (*sp == '+') {
+ rs[i].force = 1;
+ sp++;
+ }
ep = strchr(sp, ':');
if (ep) {
dp = ep + 1;
error("dst ref %s receives from more than one src.",
matched_dst->name);
}
- else
+ else {
matched_dst->peer_ref = matched_src;
+ matched_dst->force = rs[i].force;
+ }
}
return -errs;
}
return PROTO_SSH;
if (!strcmp(name, "git"))
return PROTO_GIT;
+ if (!strcmp(name, "git+ssh"))
+ return PROTO_SSH;
+ if (!strcmp(name, "ssh+git"))
+ return PROTO_SSH;
die("I don't handle protocol '%s'", name);
}
#define STR_(s) # s
#define STR(s) STR_(s)
+#ifndef NO_IPV6
+
static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
{
int sockfd = -1;
return 0;
}
+#else /* NO_IPV6 */
+
+static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
+{
+ int sockfd = -1;
+ char *colon, *end;
+ char *port = STR(DEFAULT_GIT_PORT), *ep;
+ struct hostent *he;
+ struct sockaddr_in sa;
+ char **ap;
+ unsigned int nport;
+
+ 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;
+ }
+
+
+ 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\n", port);
+ nport = se->s_port;
+ }
+
+ for (ap = he->h_addr_list; *ap; ap++) {
+ sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
+ if (sockfd < 0)
+ 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) {
+ close(sockfd);
+ sockfd = -1;
+ continue;
+ }
+ break;
+ }
+
+ if (sockfd < 0)
+ die("unable to connect a socket (%s)", strerror(errno));
+
+ fd[0] = sockfd;
+ fd[1] = sockfd;
+ packet_write(sockfd, "%s %s\n", prog, path);
+ return 0;
+}
+
+#endif /* NO_IPV6 */
+
+static char *git_proxy_command = NULL;
+static const char *rhost_name = NULL;
+static int rhost_len;
+
+static int git_proxy_command_options(const char *var, const char *value)
+{
+ if (!strcmp(var, "core.gitproxy")) {
+ const char *for_pos;
+ int matchlen = -1;
+ int hostlen;
+
+ if (git_proxy_command)
+ return 0;
+ /* [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 = xmalloc(matchlen + 1);
+ memcpy(git_proxy_command, value, matchlen);
+ git_proxy_command[matchlen] = 0;
+ }
+ return 0;
+ }
+
+ return git_default_config(var, value);
+}
+
+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;
+ return (git_proxy_command && *git_proxy_command);
+}
+
+static int git_proxy_connect(int fd[2], const char *prog, char *host, char *path)
+{
+ char *port = STR(DEFAULT_GIT_PORT);
+ char *colon, *end;
+ int pipefd[2][2];
+ pid_t pid;
+
+ 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 (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
+ die("unable to create pipe pair for communication");
+ pid = fork();
+ if (!pid) {
+ 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]);
+ execlp(git_proxy_command, git_proxy_command, host, port, NULL);
+ die("exec failed");
+ }
+ fd[0] = pipefd[0][0];
+ fd[1] = pipefd[1][1];
+ close(pipefd[0][1]);
+ close(pipefd[1][0]);
+ packet_write(fd[1], "%s %s\n", prog, path);
+ return pid;
+}
+
/*
* Yeah, yeah, fixme. Need to pass in the heads etc.
*/
int git_connect(int fd[2], char *url, const char *prog)
{
char command[1024];
- char *host, *path;
- char *colon;
+ char *host, *path = url;
+ char *end;
+ int c;
int pipefd[2][2];
pid_t pid;
- enum protocol protocol;
-
- host = NULL;
- path = url;
- colon = strchr(url, ':');
- protocol = PROTO_LOCAL;
- if (colon) {
- *colon = 0;
+ enum protocol protocol = PROTO_LOCAL;
+ int free_path = 0;
+
+ host = strstr(url, "://");
+ if(host) {
+ *host = '\0';
+ protocol = get_protocol(url);
+ host += 3;
+ c = '/';
+ } else {
host = url;
- path = colon+1;
- protocol = PROTO_SSH;
- if (!memcmp(path, "//", 2)) {
- char *slash = strchr(path + 2, '/');
- if (slash) {
- int nr = slash - path - 2;
- memmove(path, path+2, nr);
- path[nr] = 0;
- protocol = get_protocol(url);
- host = path;
- path = slash;
- }
+ c = ':';
+ }
+
+ if (host[0] == '[') {
+ end = strchr(host + 1, ']');
+ if (end) {
+ *end = 0;
+ end++;
+ host++;
+ } else
+ end = host;
+ } else
+ end = host;
+
+ path = strchr(end, c);
+ if (c == ':') {
+ if (path) {
+ protocol = PROTO_SSH;
+ *path++ = '\0';
+ } else
+ path = host;
+ }
+
+ 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;
+ if (path[1] == '~')
+ path++;
+ else {
+ path = strdup(ptr);
+ free_path = 1;
}
+
+ *ptr = '\0';
}
- if (protocol == PROTO_GIT)
- return git_tcp_connect(fd, prog, host, path);
+ if (protocol == PROTO_GIT) {
+ int ret;
+ if (git_use_proxy(host))
+ ret = git_proxy_connect(fd, prog, host, path);
+ else
+ ret = git_tcp_connect(fd, prog, host, path);
+ if (free_path)
+ free(path);
+ return ret;
+ }
if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
die("unable to create pipe pair for communication");
close(pipefd[0][1]);
close(pipefd[1][0]);
close(pipefd[1][1]);
- if (protocol == PROTO_SSH)
- execlp("ssh", "ssh", host, command, NULL);
- else
+ 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++;
+ execlp(ssh, ssh_basename, host, command, NULL);
+ }
+ else {
+ unsetenv(ALTERNATE_DB_ENVIRONMENT);
+ unsetenv(DB_ENVIRONMENT);
+ unsetenv(GIT_DIR_ENVIRONMENT);
+ unsetenv(GRAFT_ENVIRONMENT);
+ unsetenv(INDEX_ENVIRONMENT);
execlp("sh", "sh", "-c", command, NULL);
+ }
die("exec failed");
- }
+ }
fd[0] = pipefd[0][0];
fd[1] = pipefd[1][1];
close(pipefd[0][1]);
close(pipefd[1][0]);
+ if (free_path)
+ free(path);
return pid;
}