shithub: git9

Download patch

ref: 29bce68c99b7d73cf9b7956a797e8f2e737d8684
parent: b41a69d4bd25da331cb771f743a4227b0f68e5c0
author: Ori Bernstein <ori@eigenstate.org>
date: Sun Aug 30 20:10:25 EDT 2020

git/serve: implement push support

--- a/ref.c
+++ b/ref.c
@@ -263,7 +263,7 @@
 	osinit(&drop);
 	for(i = 0; i < nhead; i++){
 		if((o = readobject(head[i])) == nil)
-			sysfatal("read %H: %r", head[i]);
+			sysfatal("read head %H: %r", head[i]);
 		dprint(1, "twixt init: keep %H\n", o->hash);
 		e = emalloc(sizeof(Objq));
 		e->o = o;
@@ -274,7 +274,7 @@
 	}		
 	for(i = 0; i < ntail; i++){
 		if((o = readobject(tail[i])) == nil)
-			sysfatal("read %H: %r", tail[i]);
+			sysfatal("read tail %H: %r", tail[i]);
 		dprint(1, "init: drop %H\n", o->hash);
 		e = emalloc(sizeof(Objq));
 		e->o = o;
@@ -284,6 +284,7 @@
 		unref(o);
 	}
 
+	dprint(1, "finding twixt commits\n");
 	while(q != nil){
 		if(oshas(&drop, q->o->hash))
 			goto next;
--- a/serve.c
+++ b/serve.c
@@ -1,9 +1,13 @@
 #include <u.h>
 #include <libc.h>
 #include <pool.h>
+#include <ctype.h>
 
 #include "git.h"
 
+#define Packtmp ".git/objects/pack/recv.pack.tmp"
+#define Idxtmp ".git/objects/pack/recv.idx.tmp"
+
 char *pathpfx = "";
 int allowwrite;
 
@@ -51,11 +55,12 @@
 }
 
 int
-negotiate(Conn *c, Hash **head, int *nhead, Hash **tail, int *ntail)
+servnegotiate(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;
@@ -64,39 +69,61 @@
 	*tail = nil;
 	*nhead = 0;
 	*ntail = 0;
-	acked = 0;
 	while(1){
 		if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
 			goto error;
 		if(n == 0)
 			break;
-		if(strncmp(pkt, "want ", 5) == 0){
-			*head = erealloc(*head, (*nhead + 1)*sizeof(Hash));
-			if(hparse(&(*head)[*nhead], &pkt[5]) == -1){
-				fmtpkt(c, "ERR: garbled want\n");
-				goto error;
-			}
-			*nhead += 1;
+		if(strncmp(pkt, "want ", 5) != 0){
+			fmtpkt(c, "ERR  protocol garble %s\n", pkt);
+			goto error;
 		}
-			
-		if(strncmp(pkt, "have ", 5) == 0){
-			*tail = erealloc(*tail, (*ntail + 1)*sizeof(Hash));
-			if(hparse(&(*tail)[*ntail], &pkt[5]) == -1){
-				fmtpkt(c, "ERR: garbled have\n");
-				goto error;
-			}
-			if((o = readobject((*tail)[*ntail])) == nil)
-				continue;
-			if(!acked)
-				if(fmtpkt(c, "ACK %H\r\n", o->hash) == -1)
+		if(hparse(&h, &pkt[5]) == -1){
+			fmtpkt(c, "ERR  garbled want\n");
+			goto error;
+		}
+		if((o = readobject(h)) == nil){
+			fmtpkt(c, "ERR 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(strcmp(pkt, "done") == 0 || strcmp(pkt, "done\n") == 0)
+			break;
+		if(n == 0){
+			if(!acked && fmtpkt(c, "NAK\n") == -1)
 					goto error;
-			unref(o);
+		}
+		if(strncmp(pkt, "have ", 5) != 0){
+			fmtpkt(c, "ERR  protocol garble %s\n", pkt);
+			goto error;
+		}
+		if(hparse(&h, &pkt[5]) == -1){
+			fmtpkt(c, "ERR  garbled have\n");
+			goto error;
+		}
+		if((o = readobject(h)) == nil)
+			continue;
+		if(!acked){
+			if(fmtpkt(c, "ACK %H\n", h) == -1)
+				goto error;
 			acked = 1;
-			*ntail += 1;
 		}
+		unref(o);
+		*tail = erealloc(*tail, (*ntail + 1)*sizeof(Hash));
+		(*tail)[*ntail] = h;	
+		*ntail += 1;
 	}
-	if(!acked)
-		fmtpkt(c, "NAK\n");
+	if(!acked && fmtpkt(c, "NAK\n") == -1)
+		goto error;
 	return 0;
 error:
 	free(*head);
@@ -111,25 +138,251 @@
 	Object **obj;
 	int nhead, ntail, nobj;
 
-	if(negotiate(c, &head, &nhead, &tail, &ntail) == -1)
+	dprint(1, "negotiating pack\n");
+	if(servnegotiate(c, &head, &nhead, &tail, &ntail) == -1)
 		sysfatal("negotiate: %r");
 	dprint(1, "finding twixt\n");
 	if(findtwixt(head, nhead, tail, ntail, &obj, &nobj) == -1)
 		sysfatal("twixt: %r");
+	fprint(2, "DPRINT!\n");
 	dprint(1, "writing pack\n");
-	if(writepack(c->wfd, obj, nobj, &h) == -1)
+	if(nobj > 0 && writepack(c->wfd, obj, nobj, &h) == -1)
 		sysfatal("send: %r");
 	return 0;
 }
 
 int
-recvpack(Conn *c)
+validref(char *s)
 {
-	USED(c);
-	sysfatal("recvpack: noimpl");
+	if(strncmp(s, "refs/", 5) != 0)
+		return 0;
+	for(; *s != '\0'; s++)
+		if(!isalnum(*s) && strchr("/-_.", *s) == nil)
+			return 0;
+	return 1;
+}
+
+int
+recvnegotiate(Conn *c, Hash **cur, Hash **upd, char ***ref, int *nupd)
+{
+	char pkt[Pktmax], *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]);
+		*nupd += 1;
+	}		
+	return 0;
+error:
+	free(*cur);
+	free(*upd);
+	for(i = 0; i < *nupd; i++)
+		free((*ref)[i]);
+	free(*ref);
 	return -1;
 }
 
+int
+rename(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;
+}
+
+int
+checkhash(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;
+}
+
+int
+updatepack(Conn *c)
+{
+	char buf[Pktmax];
+	int n, pfd, packsz;
+	Hash h;
+
+	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 || 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\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;
+}	
+
+int
+lockrepo(void)
+{
+	int fd, i;
+
+	for(i = 0; i < 10; i++) {
+		if((fd = create(".git/index9/lock", ORDWR|OEXCL, 0644))!= -1)
+			return fd;
+		sleep(250);
+	}
+	return -1;
+}
+
+int
+updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
+{
+	char refpath[512];
+	int i, fd, ret, lockfd;
+	Hash h;
+
+	ret = -1;
+	if((lockfd = lockrepo()) == -1){
+		fmtpkt(c, "ERR repo locked\n");
+		return -1;
+	}
+	for(i = 0; i < nupd; i++){
+		if(resolveref(&h, ref[i]) == 0 && !hasheq(&h, &cur[i])){
+			fmtpkt(c, "ERR old ref changed: %s", ref[i]);
+			goto error;
+		}
+		if(snprint(refpath, sizeof(refpath), ".git/%s", ref[i]) == sizeof(refpath)){
+			fmtpkt(c, "ERR ref path too long: %s", ref[i]);
+			goto error;
+		}
+		if((fd = create(refpath, OWRITE, 0644)) == -1){
+			fmtpkt(c, "ERR open ref: %r");
+			goto error;
+		}
+		if(fprint(fd, "%H", upd[i]) == -1){
+			close(fd);
+			fmtpkt(c, "ERR upate ref: %r");
+			goto error;
+		}
+		close(fd);
+	}
+		
+	ret = 0;
+error:
+	close(lockfd);
+	remove(".git/index9/lock");
+	return ret;
+}
+
+int
+recvpack(Conn *c)
+{
+	Hash *cur, *upd;
+	char **ref;
+	int nupd;
+
+	if(recvnegotiate(c, &cur, &upd, &ref, &nupd) == -1)
+		sysfatal("negotiate refs: %r");
+	if(nupd != 0 && updatepack(c) == 0)
+		sysfatal("update pack: %r");
+	if(nupd != 0 && updaterefs(c, cur, upd, ref, nupd) == -1)
+		sysfatal("update refs: %r");
+	return 0;
+}
+
 char*
 parsecmd(char *buf, char *cmd, int ncmd)
 {
@@ -191,7 +444,7 @@
 		sysfatal("cd %s: %r", p);
 	if(access(".git", AREAD) == -1)
 		sysfatal("no git repository");
-	if(strcmp(cmd, "git-fetch-pack") == 0 && allowwrite)
+	if(strcmp(cmd, "git-receive-pack") == 0 && allowwrite)
 		recvpack(&c);
 	else if(strcmp(cmd, "git-upload-pack") == 0)
 		servpack(&c);
--- a/util.c
+++ b/util.c
@@ -248,11 +248,10 @@
 dprint(int v, char *fmt, ...)
 {
 	va_list ap;
-	int n;
 
-	if(v >= debug)
+	if(debug < v)
 		return;
 	va_start(ap, fmt);
-	n = vfprint(2, fmt, ap);
+	vfprint(2, fmt, ap);
 	va_end(ap);
 }