bb33663b40
Git9 was sloppy about telling git what commits we have. We would list the commits at the tip of the branch, but not walk down it, which means we would request too much data if our local branches were ahead of the remote. This patch changes that, sending the tips *and* the first 256 commits after them, so that git can produce a better pack for us, with fewer redundant commits.
529 lines
10 KiB
C
529 lines
10 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
#include <ctype.h>
|
|
|
|
#include "git.h"
|
|
|
|
#define Useragent "useragent git/2.24.1"
|
|
#define Contenthdr "headers Content-Type: application/x-git-%s-pack-request"
|
|
#define Accepthdr "headers Accept: application/x-git-%s-pack-result"
|
|
|
|
enum {
|
|
Nproto = 16,
|
|
Nport = 16,
|
|
Nhost = 256,
|
|
Npath = 128,
|
|
Nrepo = 64,
|
|
Nbranch = 32,
|
|
};
|
|
|
|
void
|
|
tracepkt(int v, char *pfx, char *b, int n)
|
|
{
|
|
char *f;
|
|
int o, i;
|
|
|
|
if(chattygit < v)
|
|
return;
|
|
o = 0;
|
|
f = emalloc(n*4 + 1);
|
|
for(i = 0; i < n; i++){
|
|
if(isprint(b[i])){
|
|
f[o++] = b[i];
|
|
continue;
|
|
}
|
|
f[o++] = '\\';
|
|
switch(b[i]){
|
|
case '\\': f[o++] = '\\'; break;
|
|
case '\n': f[o++] = 'n'; break;
|
|
case '\r': f[o++] = 'r'; break;
|
|
case '\v': f[o++] = 'v'; break;
|
|
case '\0': f[o++] = '0'; break;
|
|
default:
|
|
f[o++] = 'x';
|
|
f[o++] = "0123456789abcdef"[(b[i]>>4)&0xf];
|
|
f[o++] = "0123456789abcdef"[(b[i]>>0)&0xf];
|
|
break;
|
|
}
|
|
}
|
|
f[o] = '\0';
|
|
fprint(2, "%s %04x:\t%s\n", pfx, n, f);
|
|
free(f);
|
|
}
|
|
|
|
int
|
|
readpkt(Conn *c, char *buf, int nbuf)
|
|
{
|
|
char len[5];
|
|
char *e;
|
|
int n;
|
|
|
|
if(readn(c->rfd, len, 4) != 4)
|
|
sysfatal("pktline: short read from transport");
|
|
len[4] = 0;
|
|
n = strtol(len, &e, 16);
|
|
if(n == 0){
|
|
dprint(1, "=r=> 0000\n");
|
|
return 0;
|
|
}
|
|
if(e != len + 4 || n <= 4)
|
|
sysfatal("pktline: bad length '%s'", len);
|
|
n -= 4;
|
|
if(n >= nbuf)
|
|
sysfatal("pktline: undersize buffer");
|
|
if(readn(c->rfd, buf, n) != n)
|
|
return -1;
|
|
buf[n] = 0;
|
|
tracepkt(1, "=r=>", buf, n);
|
|
return n;
|
|
}
|
|
|
|
int
|
|
writepkt(Conn *c, char *buf, int nbuf)
|
|
{
|
|
char len[5];
|
|
|
|
|
|
snprint(len, sizeof(len), "%04x", nbuf + 4);
|
|
if(write(c->wfd, len, 4) != 4)
|
|
return -1;
|
|
if(write(c->wfd, buf, nbuf) != nbuf)
|
|
return -1;
|
|
tracepkt(1, "<=w=", buf, nbuf);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
fmtpkt(Conn *c, char *fmt, ...)
|
|
{
|
|
char pkt[Pktmax];
|
|
va_list ap;
|
|
int n;
|
|
|
|
va_start(ap, fmt);
|
|
n = vsnprint(pkt, sizeof(pkt), fmt, ap);
|
|
n = writepkt(c, pkt, n);
|
|
va_end(ap);
|
|
return n;
|
|
}
|
|
|
|
int
|
|
flushpkt(Conn *c)
|
|
{
|
|
dprint(1, "<=w= 0000\n");
|
|
return write(c->wfd, "0000", 4);
|
|
}
|
|
|
|
static void
|
|
grab(char *dst, int n, char *p, char *e)
|
|
{
|
|
int l;
|
|
|
|
l = e - p;
|
|
if(l >= n)
|
|
sysfatal("overlong component");
|
|
memcpy(dst, p, l);
|
|
dst[l] = 0;
|
|
}
|
|
|
|
static int
|
|
parseuri(char *uri, char *proto, char *host, char *port, char *path, char *repo)
|
|
{
|
|
char *s, *p, *q;
|
|
int n, hasport;
|
|
print("uri: \"%s\"\n", uri);
|
|
|
|
p = strstr(uri, "://");
|
|
if(p == nil)
|
|
snprint(proto, Nproto, "ssh");
|
|
else if(strncmp(uri, "git+", 4) == 0)
|
|
grab(proto, Nproto, uri + 4, p);
|
|
else
|
|
grab(proto, Nproto, uri, p);
|
|
*port = 0;
|
|
hasport = 1;
|
|
if(strcmp(proto, "git") == 0)
|
|
snprint(port, Nport, "9418");
|
|
else if(strncmp(proto, "https", 5) == 0)
|
|
snprint(port, Nport, "443");
|
|
else if(strncmp(proto, "http", 4) == 0)
|
|
snprint(port, Nport, "80");
|
|
else if(strncmp(proto, "hjgit", 5) == 0)
|
|
snprint(port, Nport, "17021");
|
|
else if(strncmp(proto, "gits", 5) == 0)
|
|
snprint(port, Nport, "9419");
|
|
else
|
|
hasport = 0;
|
|
s = (p != nil) ? p + 3 : uri;
|
|
p = nil;
|
|
if(!hasport){
|
|
p = strstr(s, ":");
|
|
if(p != nil)
|
|
p++;
|
|
}
|
|
if(p == nil)
|
|
p = strstr(s, "/");
|
|
if(p == nil || strlen(p) == 1){
|
|
werrstr("missing path");
|
|
return -1;
|
|
}
|
|
|
|
q = memchr(s, ':', p - s);
|
|
if(q){
|
|
grab(host, Nhost, s, q);
|
|
grab(port, Nport, q + 1, p);
|
|
}else{
|
|
grab(host, Nhost, s, p);
|
|
}
|
|
|
|
snprint(path, Npath, "%s", p);
|
|
if((q = strrchr(p, '/')) != nil)
|
|
p = q + 1;
|
|
if(strlen(p) == 0){
|
|
werrstr("missing repository in uri");
|
|
return -1;
|
|
}
|
|
n = strlen(p);
|
|
if(hassuffix(p, ".git"))
|
|
n -= 4;
|
|
grab(repo, Nrepo, p, p + n);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
webclone(Conn *c, char *url)
|
|
{
|
|
char buf[16];
|
|
int n, conn;
|
|
|
|
if((c->cfd = open("/mnt/web/clone", ORDWR)) < 0)
|
|
goto err;
|
|
if((n = read(c->cfd, buf, sizeof(buf)-1)) == -1)
|
|
goto err;
|
|
buf[n] = 0;
|
|
conn = atoi(buf);
|
|
|
|
/* github will behave differently based on useragent */
|
|
if(write(c->cfd, Useragent, sizeof(Useragent)) == -1)
|
|
return -1;
|
|
dprint(1, "open url %s\n", url);
|
|
if(fprint(c->cfd, "url %s", url) == -1)
|
|
goto err;
|
|
free(c->dir);
|
|
c->dir = smprint("/mnt/web/%d", conn);
|
|
return 0;
|
|
err:
|
|
if(c->cfd != -1)
|
|
close(c->cfd);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
webopen(Conn *c, char *file, int mode)
|
|
{
|
|
char path[128];
|
|
int fd;
|
|
|
|
snprint(path, sizeof(path), "%s/%s", c->dir, file);
|
|
if((fd = open(path, mode)) == -1)
|
|
return -1;
|
|
return fd;
|
|
}
|
|
|
|
static int
|
|
issmarthttp(Conn *c, char *direction)
|
|
{
|
|
char buf[Pktmax+1], svc[128];
|
|
int fd, n;
|
|
|
|
if((fd = webopen(c, "contenttype", OREAD)) == -1)
|
|
return -1;
|
|
n = readn(fd, buf, sizeof(buf) - 1);
|
|
close(fd);
|
|
if(n == -1)
|
|
return -1;
|
|
buf[n] = '\0';
|
|
snprint(svc, sizeof(svc), "application/x-git-%s-pack-advertisement", direction);
|
|
if(strcmp(svc, buf) != 0){
|
|
werrstr("dumb http protocol not supported");
|
|
return -1;
|
|
}
|
|
|
|
if((n = readpkt(c, buf, sizeof(buf))) == -1)
|
|
sysfatal("http read: %r");
|
|
buf[n] = 0;
|
|
snprint(svc, sizeof(svc), "# service=git-%s-pack\n", direction);
|
|
if(strncmp(svc, buf, n) != 0){
|
|
werrstr("invalid initial packet line");
|
|
return -1;
|
|
}
|
|
if(readpkt(c, buf, sizeof(buf)) != 0){
|
|
werrstr("protocol garble: expected flushpkt");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
dialhttp(Conn *c, char *host, char *port, char *path, char *direction)
|
|
{
|
|
char *geturl, *suff, *hsep, *psep;
|
|
|
|
suff = "";
|
|
hsep = "";
|
|
psep = "";
|
|
if(port && strlen(port) != 0)
|
|
hsep = ":";
|
|
if(path && path[0] != '/')
|
|
psep = "/";
|
|
memset(c, 0, sizeof(*c));
|
|
geturl = smprint("https://%s%s%s%s%s%s/info/refs?service=git-%s-pack", host, hsep, port, psep, path, suff, direction);
|
|
c->type = ConnHttp;
|
|
c->url = smprint("https://%s%s%s%s%s%s/git-%s-pack", host, hsep, port, psep, path, suff, direction);
|
|
c->cfd = webclone(c, geturl);
|
|
free(geturl);
|
|
if(c->cfd == -1)
|
|
return -1;
|
|
c->rfd = webopen(c, "body", OREAD);
|
|
c->wfd = -1;
|
|
if(c->rfd == -1)
|
|
return -1;
|
|
if(issmarthttp(c, direction) == -1)
|
|
return -1;
|
|
c->direction = estrdup(direction);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
dialssh(Conn *c, char *host, char *, char *path, char *direction)
|
|
{
|
|
int pid, pfd[2];
|
|
char cmd[64];
|
|
|
|
if(pipe(pfd) == -1)
|
|
sysfatal("unable to open pipe: %r");
|
|
pid = fork();
|
|
if(pid == -1)
|
|
sysfatal("unable to fork");
|
|
if(pid == 0){
|
|
close(pfd[1]);
|
|
dup(pfd[0], 0);
|
|
dup(pfd[0], 1);
|
|
snprint(cmd, sizeof(cmd), "git-%s-pack", direction);
|
|
dprint(1, "exec ssh '%s' '%s' %s\n", host, cmd, path);
|
|
execl("/bin/ssh", "ssh", host, cmd, path, nil);
|
|
sysfatal("exec: %r");
|
|
}
|
|
close(pfd[0]);
|
|
c->type = ConnSsh;
|
|
c->rfd = pfd[1];
|
|
c->wfd = dup(pfd[1], -1);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
githandshake(Conn *c, char *host, char *path, char *direction)
|
|
{
|
|
char *p, *e, cmd[512];
|
|
|
|
p = cmd;
|
|
e = cmd + sizeof(cmd);
|
|
p = seprint(p, e - 1, "git-%s-pack %s", direction, path);
|
|
if(host != nil)
|
|
p = seprint(p + 1, e, "host=%s", host);
|
|
if(writepkt(c, cmd, p - cmd + 1) == -1){
|
|
fprint(2, "failed to write message\n");
|
|
closeconn(c);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
dialhjgit(Conn *c, char *host, char *port, char *path, char *direction, int auth)
|
|
{
|
|
char *ds;
|
|
int pid, pfd[2];
|
|
|
|
if((ds = netmkaddr(host, "tcp", port)) == nil)
|
|
return -1;
|
|
if(pipe(pfd) == -1)
|
|
sysfatal("unable to open pipe: %r");
|
|
pid = fork();
|
|
if(pid == -1)
|
|
sysfatal("unable to fork");
|
|
if(pid == 0){
|
|
close(pfd[1]);
|
|
dup(pfd[0], 0);
|
|
dup(pfd[0], 1);
|
|
dprint(1, "exec tlsclient -a %s\n", ds);
|
|
if(auth)
|
|
execl("/bin/tlsclient", "tlsclient", "-a", ds, nil);
|
|
else
|
|
execl("/bin/tlsclient", "tlsclient", ds, nil);
|
|
sysfatal("exec: %r");
|
|
}
|
|
close(pfd[0]);
|
|
c->type = ConnGit9;
|
|
c->rfd = pfd[1];
|
|
c->wfd = dup(pfd[1], -1);
|
|
return githandshake(c, host, path, direction);
|
|
}
|
|
|
|
void
|
|
initconn(Conn *c, int rd, int wr)
|
|
{
|
|
c->type = ConnGit;
|
|
c->rfd = rd;
|
|
c->wfd = wr;
|
|
}
|
|
|
|
static int
|
|
dialgit(Conn *c, char *host, char *port, char *path, char *direction)
|
|
{
|
|
char *ds;
|
|
int fd;
|
|
|
|
if((ds = netmkaddr(host, "tcp", port)) == nil)
|
|
return -1;
|
|
dprint(1, "dial %s git-%s-pack %s\n", ds, direction, path);
|
|
fd = dial(ds, nil, nil, nil);
|
|
if(fd == -1)
|
|
return -1;
|
|
c->type = ConnGit;
|
|
c->rfd = fd;
|
|
c->wfd = dup(fd, -1);
|
|
return githandshake(c, host, path, direction);
|
|
}
|
|
|
|
static int
|
|
servelocal(Conn *c, char *path, char *direction)
|
|
{
|
|
int pid, pfd[2];
|
|
|
|
if(pipe(pfd) == -1)
|
|
sysfatal("unable to open pipe: %r");
|
|
pid = fork();
|
|
if(pid == -1)
|
|
sysfatal("unable to fork");
|
|
if(pid == 0){
|
|
close(pfd[1]);
|
|
dup(pfd[0], 0);
|
|
dup(pfd[0], 1);
|
|
execl("/bin/git/serve", "serve", "-w", nil);
|
|
sysfatal("exec: %r");
|
|
}
|
|
close(pfd[0]);
|
|
c->type = ConnGit;
|
|
c->rfd = pfd[1];
|
|
c->wfd = dup(pfd[1], -1);
|
|
return githandshake(c, nil, path, direction);
|
|
}
|
|
|
|
static int
|
|
localrepo(char *uri, char *path, int npath)
|
|
{
|
|
int fd;
|
|
|
|
snprint(path, npath, "%s/.git/../", uri);
|
|
fd = open(path, OREAD);
|
|
if(fd < 0)
|
|
return -1;
|
|
if(fd2path(fd, path, npath) != 0){
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
gitconnect(Conn *c, char *uri, char *direction)
|
|
{
|
|
char proto[Nproto], host[Nhost], port[Nport];
|
|
char repo[Nrepo], path[Npath];
|
|
|
|
memset(c, 0, sizeof(Conn));
|
|
c->rfd = c->wfd = c->cfd = -1;
|
|
|
|
if(localrepo(uri, path, sizeof(path)) == 0)
|
|
return servelocal(c, path, direction);
|
|
|
|
if(parseuri(uri, proto, host, port, path, repo) == -1){
|
|
werrstr("bad uri %s", uri);
|
|
return -1;
|
|
}
|
|
if(strcmp(proto, "ssh") == 0)
|
|
return dialssh(c, host, port, path, direction);
|
|
else if(strcmp(proto, "git") == 0)
|
|
return dialgit(c, host, port, path, direction);
|
|
else if(strcmp(proto, "hjgit") == 0)
|
|
return dialhjgit(c, host, port, path, direction, 1);
|
|
else if(strcmp(proto, "gits") == 0)
|
|
return dialhjgit(c, host, port, path, direction, 0);
|
|
else if(strcmp(proto, "http") == 0 || strcmp(proto, "https") == 0)
|
|
return dialhttp(c, host, port, path, direction);
|
|
werrstr("unknown protocol %s", proto);
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
writephase(Conn *c)
|
|
{
|
|
char hdr[128];
|
|
int n;
|
|
|
|
dprint(1, "start write phase\n");
|
|
if(c->type != ConnHttp)
|
|
return 0;
|
|
|
|
if(c->wfd != -1)
|
|
close(c->wfd);
|
|
if(c->cfd != -1)
|
|
close(c->cfd);
|
|
if((c->cfd = webclone(c, c->url)) == -1)
|
|
return -1;
|
|
n = snprint(hdr, sizeof(hdr), Contenthdr, c->direction);
|
|
if(write(c->cfd, hdr, n) == -1)
|
|
return -1;
|
|
n = snprint(hdr, sizeof(hdr), Accepthdr, c->direction);
|
|
if(write(c->cfd, hdr, n) == -1)
|
|
return -1;
|
|
if((c->wfd = webopen(c, "postbody", OWRITE)) == -1)
|
|
return -1;
|
|
c->rfd = -1;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
readphase(Conn *c)
|
|
{
|
|
dprint(1, "start read phase\n");
|
|
if(c->type != ConnHttp)
|
|
return 0;
|
|
if(close(c->wfd) == -1)
|
|
return -1;
|
|
if((c->rfd = webopen(c, "body", OREAD)) == -1)
|
|
return -1;
|
|
c->wfd = -1;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
closeconn(Conn *c)
|
|
{
|
|
close(c->rfd);
|
|
close(c->wfd);
|
|
switch(c->type){
|
|
case ConnGit:
|
|
break;
|
|
case ConnGit9:
|
|
case ConnSsh:
|
|
free(wait());
|
|
break;
|
|
case ConnHttp:
|
|
close(c->cfd);
|
|
break;
|
|
}
|
|
}
|