606 lines · 11.9 KB · C
RawDownload
#include <u.h>#include <libc.h>#include <ctype.h>#include <auth.h> #include "git.h" char	*pathpfx = nil;int	allowwrite; _Noreturn static voidfail(Conn *c, char *fmt, ...){	char msg[ERRMAX];	va_list ap; 	va_start(ap, fmt);	vsnprint(msg, sizeof(msg), fmt, ap);	va_end(ap);	fmtpkt(c, "ERR %s\n", msg);	sysfatal("%s", msg);} char*gethead(Hash *h, char *ref, int nref){	int fd, n;	char *s; 	if((fd = open(".git/HEAD", OREAD)) == -1)		return nil;	if((n = readn(fd, ref, nref-1)) == -1)		return nil;	ref[n] = 0;	strip(ref);	if(strncmp(ref, "ref: ", 5) != 0)		return nil;	s = ref+5;	if(resolveref(h, s) == -1)		return nil;	return s;} intshowrefs(Conn *c){	char **names, *s, buf[256];	int i, ret, nrefs;	Hash head, *refs; 	ret = -1;	nrefs = 0;	refs = nil;	names = nil; 	if((s = gethead(&head,  buf, sizeof(buf))) == nil)		memset(&head, 0, sizeof(Hash));	if(fmtpkt(c, "%H HEAD%csymref=HEAD:%s no-thin\n", head, 0, s) == -1)		goto error;	if((nrefs = listrefs(&refs, &names)) == -1)		fail(c, "listrefs: %r");	for(i = 0; i < nrefs; i++){		if(strncmp(names[i], "heads/", strlen("heads/")) != 0)			continue;		if(fmtpkt(c, "%H refs/%s\n", refs[i], names[i]) == -1)			goto error;	}	if(flushpkt(c) == -1)		goto error;	ret = 0;error:	for(i = 0; i < nrefs; i++)		free(names[i]);	free(names);	free(refs);	return ret;} intservnegotiate(Conn *c, Hash **head, int *nhead, Hash **tail, int *ntail){	char pkt[Pktmax];	int n, acked;	Object *o;	Hash h; 	if(showrefs(c) == -1)		return -1; 	*head = nil;	*tail = nil;	*nhead = 0;	*ntail = 0;	while(1){		if((n = readpkt(c, pkt, sizeof(pkt))) == -1)			goto error;		if(n == 0)			break;		if(strncmp(pkt, "want ", 5) != 0){			werrstr(" protocol garble %s", pkt);			goto error;		}		if(hparse(&h, &pkt[5]) == -1){			werrstr(" garbled want");			goto error;		}		if((o = readobject(h)) == nil){			werrstr("requested nonexistent object");			goto error;		}		unref(o);		*head = erealloc(*head, (*nhead + 1)*sizeof(Hash));		(*head)[*nhead] = h;			*nhead += 1;	} 	acked = 0;	while(1){		if((n = readpkt(c, pkt, sizeof(pkt))) == -1)			goto error;		if(strncmp(pkt, "done", 4) == 0)			break;		if(n == 0){			if(!acked && fmtpkt(c, "NAK") == -1)					goto error;		}		if(strncmp(pkt, "have ", 5) != 0){			werrstr(" protocol garble %s", pkt);			goto error;		}		if(hparse(&h, &pkt[5]) == -1){			werrstr(" garbled have");			goto error;		}		if((o = readobject(h)) == nil)			continue;		if(!acked){			if(fmtpkt(c, "ACK %H", h) == -1)				goto error;			acked = 1;		}		unref(o);		*tail = erealloc(*tail, (*ntail + 1)*sizeof(Hash));		(*tail)[*ntail] = h;			*ntail += 1;	}	if(!acked && fmtpkt(c, "NAK\n") == -1)		goto error;	return 0;error:	fmtpkt(c, "ERR %r\n");	free(*head);	free(*tail);	return -1;} intservpack(Conn *c){	Hash *head, *tail, h;	int nhead, ntail; 	dprint(1, "negotiating pack\n");	if(servnegotiate(c, &head, &nhead, &tail, &ntail) == -1)		fail(c, "negotiate: %r");	dprint(1, "writing pack\n");	if(writepack(c->wfd, head, nhead, tail, ntail, &h) == -1)		fail(c, "send: %r");	return 0;} intvalidref(char *s){	cleanname(s);	if(strncmp(s, "refs/", 5) != 0)		return 0;	return okref(s);} intrecvnegotiate(Conn *c, Hash **cur, Hash **upd, char ***ref, int *nupd){	char pkt[Pktmax], refpath[512], *sp[4];	Hash old, new;	int n, i; 	if(showrefs(c) == -1)		return -1;	*cur = nil;	*upd = nil;	*ref = nil;	*nupd = 0;	while(1){		if((n = readpkt(c, pkt, sizeof(pkt))) == -1)			goto error;		if(n == 0)			break;		if(getfields(pkt, sp, nelem(sp), 1, " \t\n\r") != 3){			fmtpkt(c, "ERR  protocol garble %s\n", pkt);			goto error;		}		if(hparse(&old, sp[0]) == -1){			fmtpkt(c, "ERR bad old hash %s\n", sp[0]);			goto error;		}		if(hparse(&new, sp[1]) == -1){			fmtpkt(c, "ERR bad new hash %s\n", sp[1]);			goto error;		}		if(!validref(sp[2])){			fmtpkt(c, "ERR invalid ref %s\n", sp[2]);			goto error;		}		*cur = erealloc(*cur, (*nupd + 1)*sizeof(Hash));		*upd = erealloc(*upd, (*nupd + 1)*sizeof(Hash));		*ref = erealloc(*ref, (*nupd + 1)*sizeof(Hash));		(*cur)[*nupd] = old;		(*upd)[*nupd] = new;		(*ref)[*nupd] = estrdup(sp[2]);		n = snprint(refpath, sizeof(refpath), ".git/%s", sp[2]);		if(n >= sizeof(refpath)-1){			fmtpkt(c, "ERR invalid ref %s\n", sp[2]);			goto error;		}		if(access(refpath, AWRITE) == -1		&& access(refpath, AEXIST) == 0){			fmtpkt(c, "ERR read-only ref %s\n", sp[2]);			goto error;		}		*nupd += 1;	}			return 0;error:	free(*cur);	free(*upd);	for(i = 0; i < *nupd; i++)		free((*ref)[i]);	free(*ref);	return -1;} intrename(char *pack, char *idx, Hash h){	char name[128], path[196];	Dir st; 	nulldir(&st);	st.name = name;	snprint(name, sizeof(name), "%H.pack", h);	snprint(path, sizeof(path), ".git/objects/pack/%s", name);	if(access(path, AEXIST) == 0)		fprint(2, "warning, pack %s already pushed\n", name);	else if(dirwstat(pack, &st) == -1)		return -1;	snprint(name, sizeof(name), "%H.idx", h);	snprint(path, sizeof(path), ".git/objects/pack/%s", name);	if(access(path, AEXIST) == 0)		fprint(2, "warning, pack %s already indexed\n", name);	else if(dirwstat(idx, &st) == -1)		return -1;	return 0;} intcheckhash(int fd, vlong sz, Hash *hcomp){	DigestState *st;	Hash hexpect;	char buf[Pktmax];	vlong n, r;	int nr;		if(sz < 28){		werrstr("undersize packfile");		return -1;	} 	st = nil;	n = 0;	if(seek(fd, 0, 0) == -1)		sysfatal("packfile seek: %r");	while(n != sz - 20){		nr = sizeof(buf);		if(sz - n - 20 < sizeof(buf))			nr = sz - n - 20;		r = readn(fd, buf, nr);		if(r != nr){			werrstr("short read");			return -1;		}		st = sha1((uchar*)buf, nr, nil, st);		n += r;	}	sha1(nil, 0, hcomp->h, st);	if(readn(fd, hexpect.h, sizeof(hexpect.h)) != sizeof(hexpect.h))		sysfatal("truncated packfile");	if(!hasheq(hcomp, &hexpect)){		werrstr("bad hash: %H != %H", *hcomp, hexpect);		return -1;	}	return 0;} intmkdir(char *dir){	char buf[ERRMAX];	int f; 	if(access(dir, AEXIST) == 0)		return 0;	if((f = create(dir, OREAD, DMDIR | 0755)) == -1){		rerrstr(buf, sizeof(buf));		if(strstr(buf, "exist") == nil)			return -1;	}	close(f);	return 0;} intmkpath(char *path){	char *p; 	p = path;	/*	 * we assume that the path has been	 * checked with validref(), so there	 * are no double '/' or odd characters.	 */	while(1){		p = strchr(p, '/');		if(p == nil)			return 0;		*p = 0;		if(mkdir(path) == -1)			return -1;		*p++ = '/';	}} intupdatepack(Conn *c){	char buf[Pktmax], packtmp[128], idxtmp[128], ebuf[ERRMAX];	int n, pfd, packsz;	Hash h; 	/* make sure the needed dirs exist */	if(mkdir(".git/objects") == -1)		return -1;	if(mkdir(".git/objects/pack") == -1)		return -1;	if(mkdir(".git/refs") == -1)		return -1;	if(mkdir(".git/refs/heads") == -1)		return -1;	snprint(packtmp, sizeof(packtmp), ".git/objects/pack/recv-%d.pack.tmp", getpid());	snprint(idxtmp, sizeof(idxtmp), ".git/objects/pack/recv-%d.idx.tmp", getpid());	if((pfd = create(packtmp, ORDWR, 0644)) == -1)		return -1;	packsz = 0;	while(1){		n = read(c->rfd, buf, sizeof(buf));		if(n == 0)			break;		if(n == -1){			rerrstr(ebuf, sizeof(ebuf));			if(strstr(ebuf, "hungup") == nil)				return -1;			break;		}		if(write(pfd, buf, n) != n)			return -1;		packsz += n;	}	if(checkhash(pfd, packsz, &h) == -1){		dprint(1, "hash mismatch\n");		goto error1;	}	if(indexpack(packtmp, idxtmp, h) == -1){		dprint(1, "indexing failed: %r\n");		goto error1;	}	if(rename(packtmp, idxtmp, h) == -1){		dprint(1, "rename failed: %r\n");		goto error2;	}	return 0; error2:	remove(idxtmp);error1:	remove(packtmp);	return -1;}	 intlockrepo(void){	int fd, i; 	for(i = 0; i < 10; i++) {		if((fd = create(".git/_lock", ORCLOSE|ORDWR|OTRUNC|OEXCL, 0644))!= -1)			return fd;		sleep(250);	}	return -1;} intupdaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd){	char refpath[512], buf[128];	int i, newidx, hadref, fd, ret, lockfd;	vlong newtm;	Object *o;	Hash h; 	ret = -1;	hadref = 0;	newidx = -1;	/*	 * Date of Magna Carta.	 * Wrong because it  was computed using	 * the proleptic gregorian calendar.	 */	newtm = -23811206400;		if((lockfd = lockrepo()) == -1){		snprint(buf, sizeof(buf), "repo locked\n");		return -1;	}	for(i = 0; i < nupd; i++){		if(resolveref(&h, ref[i]) == 0){			hadref = 1;			if(!hasheq(&h, &cur[i])){				snprint(buf, sizeof(buf), "old ref changed: %s", ref[i]);				goto error;			}		}		if(snprint(refpath, sizeof(refpath), ".git/%s", ref[i]) == sizeof(refpath)){			snprint(buf, sizeof(buf), "ref path too long: %s", ref[i]);			goto error;		}		if(hasheq(&upd[i], &Zhash)){			remove(refpath);			continue;		}		if((o = readobject(upd[i])) == nil){			snprint(buf, sizeof(buf), "update to nonexistent hash %H", upd[i]);			goto error;		}		if(o->type != GCommit){			snprint(buf, sizeof(buf), "not commit: %H", upd[i]);			goto error;		}		if(o->commit->mtime > newtm){			newtm = o->commit->mtime;			newidx = i;		}		unref(o);		if(mkpath(refpath) == -1){			snprint(buf, sizeof(buf), "create path: %r");			goto error;		}		if((fd = create(refpath, OWRITE|OTRUNC, 0644)) == -1){			snprint(buf, sizeof(buf), "open ref: %r");			goto error;		}		if(fprint(fd, "%H", upd[i]) == -1){			snprint(buf, sizeof(buf), "upate ref: %r");			close(fd);			goto error;		}		close(fd);	}	/*	 * Heuristic:	 * If there are no valid refs, and HEAD is invalid, then	 * pick the ref with the newest commits as the default	 * branch.	 *	 * Several people have been caught out by pushing to	 * a repo where HEAD named differently from what got	 * pushed, and this is going to be more of a footgun	 * when 'master', 'main', and 'front' are all in active	 * use. This should make us pick a useful default in	 * those cases, instead of silently failing.	 */	if(resolveref(&h, "HEAD") == -1 && hadref == 0 && newidx != -1){		if((fd = create(".git/HEAD", OWRITE|OTRUNC, 0644)) == -1){			snprint(buf, sizeof(buf), "open HEAD: %r");			goto error;		}		if(fprint(fd, "ref: %s", ref[newidx]) == -1){			snprint(buf, sizeof(buf), "write HEAD ref: %r");			goto error;		}		close(fd);

Lines 1–500 of 606.