shithub: git9

Download patch

ref: b41a69d4bd25da331cb771f743a4227b0f68e5c0
parent: 4859c3f71ff0650ea39d3f545f76d1f4ab16e1ba
author: Ori Bernstein <[email protected]>
date: Sun Aug 30 16:43:30 EDT 2020

git/serve: you can serve repos read-only now

--- a/git.h
+++ b/git.h
@@ -230,12 +230,14 @@
 /* object io */
 int	resolverefs(Hash **, char *);
 int	resolveref(Hash *, char *);
-int	listrefs(Hash **);
+int	listrefs(Hash **, char ***);
 Object	*ancestor(Object *, Object *);
+int	findtwixt(Hash *, int, Hash *, int, Object ***, int *);
 Object	*readobject(Hash);
 Object	*clearedobject(Hash, int);
 void	parseobject(Object *);
 int	indexpack(char *, char *, Hash);
+int	writepack(int, Object **, int, Hash*);
 int	hasheq(Hash *, Hash *);
 Object	*ref(Object *);
 void	unref(Object *);
@@ -244,6 +246,7 @@
 
 /* object sets */
 void	osinit(Objset *);
+void	osclear(Objset *);
 void	osadd(Objset *, Object *);
 int	oshas(Objset *, Hash);
 Object	*osfind(Objset *, Hash);
@@ -254,7 +257,7 @@
 void	olsfree(Objlist *);
 
 /* util functions */
-int	dprint(char *, ...);
+void	dprint(int, char *, ...);
 void	*emalloc(ulong);
 void	*erealloc(void *, ulong);
 char	*estrdup(char *);
@@ -272,6 +275,7 @@
 int	writepkt(Conn*, char*, int);
 int	flushpkt(Conn*);
 int	parseuri(char *, char *, char *, char *, char *, char *);
+void	initconn(Conn*, int, int);
 int	dialssh(Conn*, char *, char *, char *, char *);
 int	dialgit(Conn*, char *, char *, char *, char *);
 int	dialhttp(Conn*, char *, char *, char *, char *);
--- a/mkfile
+++ b/mkfile
@@ -9,6 +9,7 @@
 	repack\
 	save\
 	send\
+	serve\
 	walk
 
 RC=\
--- a/objset.c
+++ b/objset.c
@@ -13,7 +13,7 @@
 }
 
 void
-osfree(Objset *s)
+osclear(Objset *s)
 {
 	free(s->obj);
 }
--- a/pack.c
+++ b/pack.c
@@ -4,11 +4,31 @@
 
 #include "git.h"
 
-typedef struct Buf Buf;
+typedef struct Buf	Buf;
+typedef struct Objmeta	Objmeta;
+typedef struct Compout	Compout;
 
+struct Objmeta {
+	int	type;
+	char	*path;
+	vlong	mtime;
+	Hash	hash;
+
+	Object	*obj;
+	Object	*base;
+	Delta	*delta;
+	int	ndelta;
+};
+
+struct Compout {
+	Biobuf *bfd;
+	DigestState *st;
+};
+
 struct Buf {
 	int len;
 	int sz;
+	int off;
 	char *data;
 };
 
@@ -821,9 +841,9 @@
 			assert(idx != nil);
 			o = Boffset(idx);
 			if(Bseek(idx, obj->off, 0) == -1)
-				sysfatal("could not seek to object offset");
+				return nil;
 			if(readpacked(idx, obj, flag) == -1)
-				sysfatal("could not reload object %H", obj->hash);
+				return nil;
 			if(Bseek(idx, o, 0) == -1)
 				sysfatal("could not restore offset");
 			cache(obj);
@@ -1084,4 +1104,395 @@
 	free(valid);
 	Bterm(f);
 	return -1;
+}
+
+static int
+dsortcmp(void *pa, void *pb)
+{
+	Objmeta *a, *b;
+	int cmp;
+
+	a = pa;
+	b = pb;
+	if(a->type != b->type)
+		return a->type - b->type;
+	cmp = strcmp(a->path, b->path);
+	if(cmp != 0)
+		return cmp;
+	if(a->mtime != b->mtime)
+		return a->mtime - b->mtime;
+	return memcmp(a->hash.h, b->hash.h, sizeof(a->hash.h));
+}
+
+static int
+timecmp(void *pa, void *pb)
+{
+	Objmeta *a, *b;
+
+	a = pa;
+	b = pb;
+	return b->mtime - a->mtime;
+}
+
+static void
+addmeta(Objmeta **m, int *nm, int type, Hash h, char *path, vlong mtime)
+{
+	static Objset os;
+
+	*m = erealloc(*m, (*nm + 1)*sizeof(Objmeta));
+	memset(&(*m)[*nm], 0, sizeof(Objmeta));
+	(*m)[*nm].type = type;
+	(*m)[*nm].path = path;
+	(*m)[*nm].mtime = mtime;
+	(*m)[*nm].hash = h;
+	*nm += 1;
+}
+
+static int
+loadtree(Objmeta **m, int *nm, Hash tree, char *dpath, vlong mtime, Objset *has)
+{
+	Object *t, *o;
+	Dirent *e;
+	char *p;
+	int i, k;
+
+	if(oshas(has, tree))
+		return 0;
+	if((t = readobject(tree)) == nil)
+		return -1;
+	osadd(has, t);
+	addmeta(m, nm, t->type, t->hash, dpath, mtime);
+	for(i = 0; i < t->tree->nent; i++){
+		e = &t->tree->ent[i];
+		if(oshas(has, e->h))
+			continue;
+		if(e->ismod)
+			continue;
+		k = (e->mode & DMDIR) ? GTree : GBlob;
+		o = clearedobject(e->h, k);
+		p = smprint("%s/%s", dpath, e->name);
+		if(k == GBlob){
+			osadd(has, o);
+			addmeta(m, nm, k, o->hash, p, mtime);
+		}else if(loadtree(m, nm, e->h, p, mtime, has) == -1)
+			return -1;
+	}
+	unref(t);
+	return 0;
+}
+
+static int
+loadcommit(Hash h, Objset *has, Objmeta **m, int *nm)
+{
+	Object *c;
+	int r;
+
+	if(oshas(has, h))
+		return 0;
+	if((c = readobject(h)) == nil)
+		return -1;
+	osadd(has, c);
+	addmeta(m, nm, c->type, c->hash, estrdup(""), c->commit->ctime);
+	r = loadtree(m, nm, c->commit->tree, "", c->commit->ctime, has);
+	unref(c);
+	return r;
+}
+
+static int
+readmeta(Object **commits, int ncommits, Objmeta **m)
+{
+	Objset has;
+	int i, nm;
+
+	*m = nil;
+	nm = 0;
+	osinit(&has);
+	for(i = 0; i < ncommits; i++){
+		dprint(2, "loading commit %H\n", commits[i]->hash);
+		if(loadcommit(commits[i]->hash, &has, m, &nm) == -1){
+			free(*m);
+			return -1;
+		}
+	}
+	osclear(&has);
+	return nm;
+}
+
+static int
+deltasz(Delta *d, int nd)
+{
+	int i, sz;
+	sz = 32;
+	for(i = 0; i < nd; i++)
+		sz += d[i].cpy ? 7 : d[i].len + 1;
+	return sz;
+}
+
+static void
+pickdeltas(Objmeta *meta, int nmeta)
+{
+	Objmeta *m, *p;
+	Object *a, *b;
+	Delta *d;
+	int i, x, nd, sz, pcnt, best;
+
+	pcnt = 0;
+	fprint(2, "deltifying %d objects:   0%%", nmeta);
+	qsort(meta, nmeta, sizeof(Objmeta), dsortcmp);
+	for(i = 0; i < nmeta; i++){
+		m = &meta[i];
+		x = (i*100) / nmeta;
+		if(x > pcnt){
+			pcnt = x;
+			if(pcnt%10 == 0)
+				fprint(2, "\b\b\b\b%3d%%", pcnt);
+		}
+		p = meta;
+		if(i > 10)
+			p = m - 10;
+		if((a = readobject(m->hash)) == nil)
+			sysfatal("missing object %H", m->hash);
+		best = a->size;
+		m->base = nil;
+		m->delta = nil;
+		m->ndelta = 0;
+		for(; p != m; p++){
+			if((b = readobject(p->hash)) == nil)
+				sysfatal("missing object %H", p->hash);
+			d = deltify(a->data, a->size, b->data, b->size, &nd);
+			sz = deltasz(d, nd);
+			if(sz + 32 < best){
+				free(m->delta);
+				best = sz;
+				m->base = b;
+				m->delta = d;
+				m->ndelta = nd;
+			}else
+				free(d);
+			unref(b);
+		}
+		unref(a);
+	}
+	fprint(2, "\b\b\b\b100%%\n");
+}
+
+static int
+compread(void *p, void *dst, int n)
+{
+	Buf *b;
+
+	b = p;
+	if(n > b->sz - b->off)
+		n = b->sz - b->off;
+	memcpy(dst, b->data + b->off, n);
+	b->off += n;
+	return n;
+}
+
+static int
+compwrite(void *p, void *buf, int n)
+{
+	return hwrite(((Compout *)p)->bfd, buf, n, &((Compout*)p)->st);
+}
+
+static int
+hcompress(Biobuf *bfd, void *buf, int sz, DigestState **st)
+{
+	int r;
+	Buf b ={
+		.off=0,
+		.data=buf,
+		.sz=sz,
+	};
+	Compout o = {
+		.bfd = bfd,
+		.st = *st,
+	};
+
+	r = deflatezlib(&o, compwrite, &b, compread, 6, 0);
+	*st = o.st;
+	return r;
+}
+
+static void
+append(char **p, int *len, int *sz, void *seg, int nseg)
+{
+	if(*len + nseg >= *sz){
+		while(*len + nseg >= *sz)
+			*sz += *sz/2;
+		*p = erealloc(*p, *sz);
+	}
+	memcpy(*p + *len, seg, nseg);
+	*len += nseg;
+}
+
+static int
+encodedelta(Objmeta *m, Object *o, Object *b, void **pp)
+{
+	char *p, *bp, buf[16];
+	int len, sz, n, i, j;
+	Delta *d;
+
+	sz = 128;
+	len = 0;
+	p = emalloc(sz);
+
+	/* base object size */
+	buf[0] = b->size & 0x7f;
+	n = b->size >> 7;
+	for(i = 1; n > 0; i++){
+		buf[i - 1] |= 0x80;
+		buf[i] = n & 0x7f;
+		n >>= 7;
+	}
+	append(&p, &len, &sz, buf, i);
+
+	/* target object size */
+	buf[0] = o->size & 0x7f;
+	n = o->size >> 7;
+	for(i = 1; n > 0; i++){
+		buf[i - 1] |= 0x80;
+		buf[i] = n & 0x7f;
+		n >>= 7;
+	}
+	append(&p, &len, &sz, buf, i);
+	for(j = 0; j < m->ndelta; j++){
+		d = &m->delta[j];
+		if(d->cpy){
+			n = d->off;
+			bp = buf + 1;
+			buf[0] = 0x81;
+			buf[1] = 0x00;
+			for(i = 0; i < sizeof(buf); i++) {
+				buf[0] |= 1<<i;
+				*bp++ = n & 0xff;
+				n >>= 8;
+				if(n == 0)
+					break;
+			}
+
+			n = d->len;
+			if(n != 0x10000) {
+				buf[0] |= 0x1<<4;
+				for(i = 0; i < sizeof(buf)-4 && n > 0; i++){
+					buf[0] |= 1<<(i + 4);
+					*bp++ = n & 0xff;
+					n >>= 8;
+				}
+			}
+			append(&p, &len, &sz, buf, bp - buf);
+		}else{
+			n = 0;
+			while(n != d->len){
+				buf[0] = (d->len - n < 127) ? d->len - n : 127;
+				append(&p, &len, &sz, buf, 1);
+				append(&p, &len, &sz, o->data + d->off + n, buf[0]);
+				n += buf[0];
+			}
+		}
+	}
+	*pp = p;
+	return len;
+}
+
+static int
+genpack(int fd, Objmeta *meta, int nmeta, Hash *h)
+{
+	int i, j, n, x, res, pcnt, len, ret;
+	DigestState *st;
+	Biobuf *bfd;
+	Objmeta *m;
+	Object *o;
+	char *p, buf[16];
+
+	st = nil;
+	ret = -1;
+	pcnt = 0;
+	if((fd = dup(fd, -1)) == -1)
+		return -1;
+	if((bfd = Bfdopen(fd, OWRITE)) == nil)
+		return -1;
+	if(hwrite(bfd, "PACK", 4, &st) == -1)
+		return -1;
+	PUTBE32(buf, 2);
+	if(hwrite(bfd, buf, 4, &st) == -1)
+		return -1;
+	PUTBE32(buf, nmeta);
+	if(hwrite(bfd, buf, 4, &st) == -1)
+		return -1;
+	qsort(meta, nmeta, sizeof(Objmeta), timecmp);
+	fprint(2, "writing %d objects:   0%%", nmeta);
+	for(i = 0; i < nmeta; i++){
+		m = &meta[i];
+		x = (i*100) / nmeta;
+		if(x > pcnt){
+			pcnt = x;
+			if(pcnt%10 == 0)
+				fprint(2, "\b\b\b\b%3d%%", pcnt);
+		}
+		if((o = readobject(m->hash)) == nil)
+			return -1;
+		if(m->delta == nil){
+			len = o->size;
+			buf[0] = o->type << 4;
+			buf[0] |= len & 0xf;
+			len >>= 4;
+			for(j = 1; len != 0; j++){
+				assert(j < sizeof(buf));
+				buf[j-1] |= 0x80;
+				buf[j] = len & 0x7f;
+				len >>= 7;
+			}
+			hwrite(bfd, buf, j, &st);
+			if(hcompress(bfd, o->data, o->size, &st) == -1)
+				goto error;
+		}else{
+			n = encodedelta(m, o, m->base, &p);
+			len = n;
+			buf[0] = GRdelta << 4;
+			buf[0] |= len & 0xf;
+			len >>= 4;
+			for(j = 1; len != 0; j++){
+				assert(j < sizeof(buf));
+				buf[j-1] |= 0x80;
+				buf[j] = len & 0x7f;
+				len >>= 7;
+			}
+			hwrite(bfd, buf, j, &st);
+			hwrite(bfd, m->base->hash.h, sizeof(m->base->hash.h), &st);
+			res = hcompress(bfd, p, n, &st);
+			free(p);
+			if(res == -1)
+				goto error;
+		}
+		unref(o);
+	}
+	fprint(2, "\b\b\b\b100%%\n");
+	sha1(nil, 0, h->h, st);
+	if(Bwrite(bfd, h->h, sizeof(h->h)) == -1)
+		goto error;
+	ret = 0;
+error:
+	if(Bterm(bfd) == -1)
+		return -1;
+	return ret;
+}
+
+int
+writepack(int fd, Object **obj, int nobj, Hash *h)
+{
+	Objmeta *meta;
+	int nmeta;
+
+	dprint(1, "reading meta\n");
+	if((nmeta = readmeta(obj, nobj, &meta)) == -1)
+		return -1;
+	dprint(1, "picking deltas\n");
+	pickdeltas(meta, nmeta);
+	dprint(1, "generating pack\n");
+	if(genpack(fd, meta, nmeta, h) == -1){
+		free(meta);
+		return -1;
+	}
+	return 0;
 }
--- a/proto.c
+++ b/proto.c
@@ -286,6 +286,14 @@
 	return 0;
 }
 
+void
+initconn(Conn *c, int rd, int wr)
+{
+	c->type = ConnRaw;
+	c->rfd = rd;
+	c->wfd = wr;
+}
+
 int
 writephase(Conn *c)
 {
@@ -337,7 +345,6 @@
 	close(c->wfd);
 	switch(c->type){
 	case ConnRaw:
-
 		break;
 	case ConnSsh:
 		free(wait());
--- a/ref.c
+++ b/ref.c
@@ -6,7 +6,14 @@
 
 typedef struct Eval	Eval;
 typedef struct XObject	XObject;
+typedef struct Objq	Objq;
 
+enum {
+	Blank,
+	Keep,
+	Drop,
+};
+
 struct Eval {
 	char	*str;
 	char	*p;
@@ -22,6 +29,11 @@
 	XObject	*next;
 };
 
+struct Objq {
+	Objq	*next;
+	Object	*o;
+	int	color;
+};
 
 void
 eatspace(Eval *ev)
@@ -212,8 +224,117 @@
 	return 0;
 }
 
+static int
+repaint(Objset *keep, Objset *drop, Object *o)
+{
+	Object *p;
+	int i;
 
+	if(!oshas(keep, o->hash) && !oshas(drop, o->hash)){
+		dprint(2, "repaint: blank => drop %H\n", o->hash);
+		osadd(drop, o);
+		return 0;
+	}
+	if(oshas(keep, o->hash))
+		dprint(2, "repaint: keep => drop %H\n", o->hash);
+	osadd(drop, o);
+	for(i = 0; i < o->commit->nparent; i++){
+		if((p = readobject(o->commit->parent[i])) == nil)
+			return -1;
+		if(repaint(keep, drop, p) == -1)
+			return -1;
+		unref(p);
+	}
+	return 0;
+}
+
 int
+findtwixt(Hash *head, int nhead, Hash *tail, int ntail, Object ***res, int *nres)
+{
+	Objq *q, *e, *n, **p;
+	Objset keep, drop;
+	Object *o, *c;
+	int i;
+
+	e = nil;
+	q = nil;
+	p = &q;
+	osinit(&keep);
+	osinit(&drop);
+	for(i = 0; i < nhead; i++){
+		if((o = readobject(head[i])) == nil)
+			sysfatal("read %H: %r", head[i]);
+		dprint(1, "twixt init: keep %H\n", o->hash);
+		e = emalloc(sizeof(Objq));
+		e->o = o;
+		e->color = Keep;
+		*p = e;
+		p = &e->next;
+		unref(o);
+	}		
+	for(i = 0; i < ntail; i++){
+		if((o = readobject(tail[i])) == nil)
+			sysfatal("read %H: %r", tail[i]);
+		dprint(1, "init: drop %H\n", o->hash);
+		e = emalloc(sizeof(Objq));
+		e->o = o;
+		e->color = Drop;
+		*p = e;
+		p = &e->next;
+		unref(o);
+	}
+
+	while(q != nil){
+		if(oshas(&drop, q->o->hash))
+			goto next;
+
+		if(oshas(&keep, q->o->hash) && q->color == Drop){
+			if(repaint(&keep, &drop, q->o) == -1)
+				goto error;
+		}else{
+			dprint(2, "visit: %s %H\n", q->color == Keep ? "keep" : "drop", q->o->hash);
+			if(q->color == Keep)
+				osadd(&keep, q->o);
+			else
+				osadd(&drop, q->o);
+			for(i = 0; i < q->o->commit->nparent; i++){
+				if((c = readobject(q->o->commit->parent[i])) == nil)
+					goto error;
+				dprint(2, "enqueue: %s %H\n", q->color == Keep ? "keep" : "drop", c->hash);
+				n = emalloc(sizeof(Objq));
+				n->color = q->color;
+				n->next = nil;
+				n->o = c;
+				e->next = n;
+				e = n;
+				unref(c);
+			}
+		}
+next:
+		n = q->next;
+		free(q);
+		q = n;
+	}
+	*res = emalloc(keep.nobj*sizeof(Object*));
+	*nres = 0;
+	for(i = 0; i < keep.sz; i++){
+		if(keep.obj[i] != nil && !oshas(&drop, keep.obj[i]->hash)){
+			(*res)[*nres] = keep.obj[i];
+			(*nres)++;
+		}
+	}
+	osclear(&keep);
+	osclear(&drop);
+	return 0;
+error:
+	for(; q != nil; q = n) {
+		n = q->next;
+		free(q);
+	}
+	return -1;
+}
+
+static int
 parent(Eval *ev)
 {
 	Object *o, *p;
@@ -231,7 +352,7 @@
 	return 0;
 }
 
-int
+static int
 unwind(Eval *ev, Object **obj, int *idx, int nobj, Object **p, Objset *set, int keep)
 {
 	int i;
@@ -252,7 +373,7 @@
 	return -1;
 }
 
-int
+static int
 range(Eval *ev)
 {
 	Object *a, *b, *p, **all;
@@ -447,42 +568,46 @@
 }
 
 int
-readrefdir(Hash **refs, int *nrefs, char *dpath, char *dname)
+readrefdir(Hash **refs, char ***names, int *nrefs, char *dpath, char *dname)
 {
 	Dir *d, *e, *dir;
-	char *path, *name;
+	char *path, *name, *sep;
 	int ndir;
 
 	if((ndir = slurpdir(dpath, &dir)) == -1)
 		return -1;
+	sep = (*dname == '\0') ? "" : "/";
 	e = dir + ndir;
 	for(d = dir; d != e; d++){
 		path = smprint("%s/%s", dpath, d->name);
-		name = smprint("%s/%s", dname, d->name);
+		name = smprint("%s%s%s", dname, sep, d->name);
 		if(d->mode & DMDIR) {
-			if(readrefdir(refs, nrefs, path, name) == -1)
-				goto nextiter;
+			if(readrefdir(refs, names, nrefs, path, name) == -1)
+				goto noref;
 		}else{
 			*refs = erealloc(*refs, (*nrefs + 1)*sizeof(Hash));
-			if(resolveref(*refs + *nrefs, name) == -1)
-				goto nextiter;
+			*names = erealloc(*names, (*nrefs + 1)*sizeof(char*));
+			if(resolveref(&(*refs)[*nrefs], name) == -1)
+				goto noref;
+			(*names)[*nrefs] = name;
 			*nrefs += 1;
+			goto next;
 		}
-nextiter:		
-		free(name);
-		free(path);
+noref:		free(name);
+next:		free(path);
 	}
 	return 0;
 }
 
 int
-listrefs(Hash **refs)
+listrefs(Hash **refs, char ***names)
 {
 	int nrefs;
 
 	*refs = nil;
+	*names = nil;
 	nrefs = 0;
-	if(readrefdir(refs, &nrefs, ".git/refs", "") == -1){
+	if(readrefdir(refs, names, &nrefs, ".git/refs", "") == -1){
 		free(*refs);
 		return -1;
 	}
--- a/repack.c
+++ b/repack.c
@@ -6,441 +6,7 @@
 
 #define TMPPATH(suff) (".git/objects/pack/repack."suff)
 
-typedef struct Objmeta	Objmeta;
-typedef struct Objq	Objq;
-typedef struct Buf	Buf;
-typedef struct Compout	Compout;
-
-struct Objmeta {
-	int	type;
-	char	*path;
-	vlong	mtime;
-	Hash	hash;
-
-	Object	*obj;
-	Object	*base;
-	Delta	*delta;
-	int	ndelta;
-};
-
-struct Objq {
-	Objq	*next;
-	Object	*obj;
-};
-
-struct Buf {
-	int off;
-	int sz;
-	uchar *data;
-};
-
-struct Compout {
-	Biobuf *bfd;
-	DigestState *st;
-};
-
-void
-usage(void)
-{
-	fprint(2, "usage: %s [-d]\n", argv0);
-	exits("usage");
-}
-
-static int
-dsortcmp(void *pa, void *pb)
-{
-	Objmeta *a, *b;
-	int cmp;
-
-	a = pa;
-	b = pb;
-	if(a->type != b->type)
-		return a->type - b->type;
-	cmp = strcmp(a->path, b->path);
-	if(cmp != 0)
-		return cmp;
-	if(a->mtime != b->mtime)
-		return a->mtime - b->mtime;
-	return memcmp(a->hash.h, b->hash.h, sizeof(a->hash.h));
-}
-
-static int
-timecmp(void *pa, void *pb)
-{
-	Objmeta *a, *b;
-
-	a = pa;
-	b = pb;
-	return b->mtime - a->mtime;
-}
-
-static void
-addmeta(Objmeta **m, int *nm, int type, Hash h, char *path, vlong mtime)
-{
-	*m = erealloc(*m, (*nm + 1)*sizeof(Objmeta));
-	memset(&(*m)[*nm], 0, sizeof(Objmeta));
-	(*m)[*nm].type = type;
-	(*m)[*nm].path = path;
-	(*m)[*nm].mtime = mtime;
-	(*m)[*nm].hash = h;
-	*nm += 1;
-}
-
-static int
-loadtree(Objmeta **m, int *nm, Hash tree, char *dpath, vlong mtime, Objset *has)
-{
-	Object *t, *o;
-	Dirent *e;
-	char *p;
-	int i, k;
-
-	if(oshas(has, tree))
-		return 0;
-	if((t = readobject(tree)) == nil)
-		return -1;
-	osadd(has, t);
-	addmeta(m, nm, t->type, t->hash, dpath, mtime);
-	for(i = 0; i < t->tree->nent; i++){
-		e = &t->tree->ent[i];
-		if(oshas(has, e->h))
-			continue;
-		if(e->ismod)
-			continue;
-		k = (e->mode & DMDIR) ? GTree : GBlob;
-		o = clearedobject(e->h, k);
-		p = smprint("%s/%s", dpath, e->name);
-		addmeta(m, nm, k, o->hash, p, mtime);
-		if(k == GBlob)
-			osadd(has, o);
-		else if(loadtree(m, nm, e->h, p, mtime, has) == -1)
-			return -1;
-	}
-	unref(t);
-	return 0;
-}
-
-static int
-loadcommit(Objmeta **m, int *nm, Hash h, Objset *has)
-{
-	Object *c, *p;
-	Objq *q, *e, *t, *n;
-	int i;
-
-	if((c = readobject(h)) == nil)
-		return -1;
-	if(c->type != GCommit)
-		sysfatal("object %H not commit", c->hash);
-	q = emalloc(sizeof(Objq));
-	e = q;
-	q->next = nil;
-	q->obj = c;
-	for(; q != nil; q = n){
-		c = q->obj;
-		if(oshas(has, c->hash))
-			goto nextiter;
-		osadd(has, c);
-		for(i = 0; i < c->commit->nparent; i++){
-			if((p = readobject(c->commit->parent[i])) == nil)
-				return -1;
-			t = emalloc(sizeof(Objq));
-			t->next = nil;
-			t->obj = p;
-			e->next = t;
-			e = t;
-		}
-		addmeta(m, nm, c->type, c->hash, estrdup(""), c->commit->ctime);
-		if(loadtree(m, nm, c->commit->tree, "", c->commit->ctime, has) == -1)
-			goto error;
-nextiter:
-		n = q->next;
-		unref(q->obj);
-		free(q);
-	}
-	return 0;
-error:
-	for(; q != nil; q = n) {
-		n = q->next;
-		free(q);
-	}
-	return -1;
-}
-
-static int
-readmeta(Objmeta **m, Hash *roots, int nroots, Hash * /*leaves */, int /*nleaves*/)
-{
-	Objset has;
-	int i, nm;
-
-	*m = nil;
-	nm = 0;
-	osinit(&has);
-	for(i = 0; i < nroots; i++)
-		if(loadcommit(m, &nm, roots[i], &has) == -1){
-			free(*m);
-			return -1;
-		}
-	return nm;
-}
-
-static int
-deltasz(Delta *d, int nd)
-{
-	int i, sz;
-	sz = 32;
-	for(i = 0; i < nd; i++)
-		sz += d[i].cpy ? 7 : d[i].len + 1;
-	return sz;
-}
-
-static void
-pickdeltas(Objmeta *meta, int nmeta)
-{
-	Objmeta *m, *p;
-	Object *a, *b;
-	Delta *d;
-	int i, nd, sz, best;
-
-	qsort(meta, nmeta, sizeof(Objmeta), dsortcmp);
-	for(i = 0; i < nmeta; i++){
-		m = &meta[i];
-		p = meta;
-		if(i > 10)
-			p = m - 10;
-		if((a = readobject(m->hash)) == nil)
-			sysfatal("missing object %H", m->hash);
-		best = a->size;
-		m->base = nil;
-		m->delta = nil;
-		m->ndelta = 0;
-		for(; p != m; p++){
-			if((b = readobject(p->hash)) == nil)
-				sysfatal("missing object %H", p->hash);
-			d = deltify(a->data, a->size, b->data, b->size, &nd);
-			sz = deltasz(d, nd);
-			if(sz + 32 < best){
-				free(m->delta);
-				best = sz;
-				m->base = b;
-				m->delta = d;
-				m->ndelta = nd;
-			}else
-				free(d);
-			unref(b);
-		}
-		unref(a);
-	}
-}
-
-static int
-hwrite(Biobuf *b, void *buf, int len, DigestState **st)
-{
-	*st = sha1(buf, len, nil, *st);
-	return Bwrite(b, buf, len);
-}
-
 int
-compread(void *p, void *dst, int n)
-{
-	Buf *b;
-
-	b = p;
-	if(n > b->sz - b->off)
-		n = b->sz - b->off;
-	memcpy(dst, b->data + b->off, n);
-	b->off += n;
-	return n;
-}
-
-int
-compwrite(void *p, void *buf, int n)
-{
-	return hwrite(((Compout *)p)->bfd, buf, n, &((Compout*)p)->st);
-}
-
-int
-hcompress(Biobuf *bfd, void *buf, int sz, DigestState **st)
-{
-	int r;
-	Buf b ={
-		.off=0,
-		.data=buf,
-		.sz=sz,
-	};
-	Compout o = {
-		.bfd = bfd,
-		.st = *st,
-	};
-
-	r = deflatezlib(&o, compwrite, &b, compread, 6, 0);
-	*st = o.st;
-	return r;
-}
-
-void
-append(char **p, int *len, int *sz, void *seg, int nseg)
-{
-	if(*len + nseg >= *sz){
-		while(*len + nseg >= *sz)
-			*sz += *sz/2;
-		*p = erealloc(*p, *sz);
-	}
-	memcpy(*p + *len, seg, nseg);
-	*len += nseg;
-}
-
-int
-encodedelta(Objmeta *m, Object *o, Object *b, void **pp)
-{
-	char *p, *bp, buf[16];
-	int len, sz, n, i, j;
-	Delta *d;
-
-	sz = 128;
-	len = 0;
-	p = emalloc(sz);
-
-	/* base object size */
-	buf[0] = b->size & 0x7f;
-	n = b->size >> 7;
-	for(i = 1; n > 0; i++){
-		buf[i - 1] |= 0x80;
-		buf[i] = n & 0x7f;
-		n >>= 7;
-	}
-	append(&p, &len, &sz, buf, i);
-
-	/* target object size */
-	buf[0] = o->size & 0x7f;
-	n = o->size >> 7;
-	for(i = 1; n > 0; i++){
-		buf[i - 1] |= 0x80;
-		buf[i] = n & 0x7f;
-		n >>= 7;
-	}
-	append(&p, &len, &sz, buf, i);
-	for(j = 0; j < m->ndelta; j++){
-		d = &m->delta[j];
-		if(d->cpy){
-			n = d->off;
-			bp = buf + 1;
-			buf[0] = 0x81;
-			buf[1] = 0x00;
-			for(i = 0; i < sizeof(buf); i++) {
-				buf[0] |= 1<<i;
-				*bp++ = n & 0xff;
-				n >>= 8;
-				if(n == 0)
-					break;
-			}
-
-			n = d->len;
-			if(n != 0x10000) {
-				buf[0] |= 0x1<<4;
-				for(i = 0; i < sizeof(buf)-4 && n > 0; i++){
-					buf[0] |= 1<<(i + 4);
-					*bp++ = n & 0xff;
-					n >>= 8;
-				}
-			}
-			append(&p, &len, &sz, buf, bp - buf);
-		}else{
-			n = 0;
-			while(n != d->len){
-				buf[0] = (d->len - n < 127) ? d->len - n : 127;
-				append(&p, &len, &sz, buf, 1);
-				append(&p, &len, &sz, o->data + d->off + n, buf[0]);
-				n += buf[0];
-			}
-		}
-	}
-	*pp = p;
-	return len;
-}
-
-static int
-writepack(int fd, Objmeta *meta, int nmeta, Hash *h)
-{
-	int i, j, n, x, res, pcnt, len, ret;
-	DigestState *st;
-	Biobuf *bfd;
-	Objmeta *m;
-	Object *o;
-	char *p, buf[16];
-
-	st = nil;
-	ret = -1;
-	pcnt = 0;
-	if((fd = dup(fd, -1)) == -1)
-		return -1;
-	if((bfd = Bfdopen(fd, OWRITE)) == nil)
-		return -1;
-	if(hwrite(bfd, "PACK", 4, &st) == -1)
-		return -1;
-	PUTBE32(buf, 2);
-	if(hwrite(bfd, buf, 4, &st) == -1)
-		return -1;
-	PUTBE32(buf, nmeta);
-	if(hwrite(bfd, buf, 4, &st) == -1)
-		return -1;
-	qsort(meta, nmeta, sizeof(Objmeta), timecmp);
-	fprint(2, "deltifying %d objects:   0%%", nmeta);
-	for(i = 0; i < nmeta; i++){
-		m = &meta[i];
-		x = (i*100) / nmeta;
-		if(x > pcnt){
-			pcnt = x;
-			if(pcnt%10 == 0)
-				fprint(2, "\b\b\b\b%3d%%", pcnt);
-		}
-		if((o = readobject(m->hash)) == nil)
-			return -1;
-		if(m->delta == nil){
-			len = o->size;
-			buf[0] = o->type << 4;
-			buf[0] |= len & 0xf;
-			len >>= 4;
-			for(j = 1; len != 0; j++){
-				assert(j < sizeof(buf));
-				buf[j-1] |= 0x80;
-				buf[j] = len & 0x7f;
-				len >>= 7;
-			}
-			hwrite(bfd, buf, j, &st);
-			if(hcompress(bfd, o->data, o->size, &st) == -1)
-				goto error;
-		}else{
-			n = encodedelta(m, o, m->base, &p);
-			len = n;
-			buf[0] = GRdelta << 4;
-			buf[0] |= len & 0xf;
-			len >>= 4;
-			for(j = 1; len != 0; j++){
-				assert(j < sizeof(buf));
-				buf[j-1] |= 0x80;
-				buf[j] = len & 0x7f;
-				len >>= 7;
-			}
-			hwrite(bfd, buf, j, &st);
-			hwrite(bfd, m->base->hash.h, sizeof(m->base->hash.h), &st);
-			res = hcompress(bfd, p, n, &st);
-			free(p);
-			if(res == -1)
-				goto error;
-		}
-		unref(o);
-	}
-	fprint(2, "\b\b\b\b100%%\n");
-	sha1(nil, 0, h->h, st);
-	if(Bwrite(bfd, h->h, sizeof(h->h)) == -1)
-		goto error;
-	ret = 0;
-error:
-	if(Bterm(bfd) == -1)
-		return -1;
-	return ret;
-}
-
-int
 cleanup(Hash h)
 {
 	char newpfx[42], dpath[256], fpath[256];
@@ -472,12 +38,19 @@
 }
 
 void
+usage(void)
+{
+	fprint(2, "usage: %s [-d]\n", argv0);
+	exits("usage");
+}
+
+void
 main(int argc, char **argv)
 {
-	int fd, nmeta, nrefs;
-	Objmeta *meta;
+	char path[128], **names;
+	int fd, nobj, nrefs;
 	Hash *refs, h;
-	char path[128];
+	Object **obj;
 	Dir rn;
 
 	ARGBEGIN{
@@ -490,14 +63,13 @@
 
 	gitinit();
 	refs = nil;
-	if((nrefs = listrefs(&refs)) == -1)
+	if((nrefs = listrefs(&refs, &names)) == -1)
 		sysfatal("load refs: %r");
-	if((nmeta = readmeta(&meta, refs, nrefs, nil, 0)) == -1)
-		sysfatal("read object metadata: %r");
-	pickdeltas(meta, nmeta);
+	if(findtwixt(refs, nrefs, nil, 0, &obj, &nobj) == -1)
+		sysfatal("load twixt: %r");
 	if((fd = create(TMPPATH("pack.tmp"), OWRITE, 0644)) == -1)
 		sysfatal("open %s: %r", TMPPATH("pack.tmp"));
-	if(writepack(fd, meta, nmeta, &h) == -1)
+	if(writepack(fd, obj, nobj, &h) == -1)
 		sysfatal("writepack: %r");
 	if(indexpack(TMPPATH("pack.tmp"), TMPPATH("idx.tmp"), h) == -1)
 		sysfatal("indexpack: %r");
--- a/send.c
+++ b/send.c
@@ -156,7 +156,7 @@
 }
 
 int
-writepack(Conn *c, Update *upd, int nupd)
+writepackdata(Conn *c, Update *upd, int nupd)
 {
 	Objset send, skip;
 	Object *o, *p;
@@ -372,7 +372,7 @@
 	if(send){
 		if(chattygit)
 			fprint(2, "sending pack...\n");
-		if(writepack(c, upd, nupd) == -1)
+		if(writepackdata(c, upd, nupd) == -1)
 			return -1;
 
 		if(readphase(c) == -1)
--- /dev/null
+++ b/serve.c
@@ -1,0 +1,201 @@
+#include <u.h>
+#include <libc.h>
+#include <pool.h>
+
+#include "git.h"
+
+char *pathpfx = "";
+int allowwrite;
+
+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
+showrefs(Conn *c)
+{
+	int i, ret, nrefs;
+	Hash head, *refs;
+	char **names;
+
+	ret = -1;
+	nrefs = 0;
+	if(resolveref(&head, "HEAD") != -1) {
+		if(fmtpkt(c, "%H HEAD", head) == -1)
+			goto error;
+	}
+	if((nrefs = listrefs(&refs, &names)) == -1)
+		sysfatal("listrefs: %r");
+	for(i = 0; i < nrefs; i++)
+		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;
+}
+
+int
+negotiate(Conn *c, Hash **head, int *nhead, Hash **tail, int *ntail)
+{
+	char pkt[Pktmax];
+	int n, acked;
+	Object *o;
+
+	if(showrefs(c) == -1)
+		return -1;
+
+	*head = nil;
+	*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, "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)
+					goto error;
+			unref(o);
+			acked = 1;
+			*ntail += 1;
+		}
+	}
+	if(!acked)
+		fmtpkt(c, "NAK\n");
+	return 0;
+error:
+	free(*head);
+	free(*tail);
+	return -1;
+}
+
+int
+servpack(Conn *c)
+{
+	Hash *head, *tail, h;
+	Object **obj;
+	int nhead, ntail, nobj;
+
+	if(negotiate(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");
+	dprint(1, "writing pack\n");
+	if(writepack(c->wfd, obj, nobj, &h) == -1)
+		sysfatal("send: %r");
+	return 0;
+}
+
+int
+recvpack(Conn *c)
+{
+	USED(c);
+	sysfatal("recvpack: noimpl");
+	return -1;
+}
+
+char*
+parsecmd(char *buf, char *cmd, int ncmd)
+{
+	int i;
+	char *p;
+
+	for(p = buf, i = 0; *p && i < ncmd - 1; i++, p++){
+		if(*p == ' ' || *p == '\t'){
+			cmd[i] = 0;
+			break;
+		}
+		cmd[i] = *p;
+	}
+	while(*p == ' ' || *p == '\t')
+		p++;
+	return p;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-dw]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char *p, cmd[32], buf[512], path[512];
+	Conn c;
+
+	ARGBEGIN{
+	case 'd':
+		debug++;
+		chattygit++;
+		break;
+	case 'r':
+		pathpfx = EARGF(usage());
+		if(*pathpfx != '/')
+			sysfatal("path prefix must begin with '/'");
+		break;
+	case 'w':
+		allowwrite++;
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND;
+
+	gitinit();
+	initconn(&c, 0, 1);
+
+	if(readpkt(&c, buf, sizeof(buf)) == -1)
+		sysfatal("readpkt: %r");
+	p = parsecmd(buf, cmd, sizeof(cmd));
+	if(snprint(path, sizeof(path), "%s/%s", pathpfx, p) == sizeof(path))
+		sysfatal("%s: path too long\n", p);
+	if(chdir(path) == -1)
+		sysfatal("cd %s: %r", p);
+	if(access(".git", AREAD) == -1)
+		sysfatal("no git repository");
+	if(strcmp(cmd, "git-fetch-pack") == 0 && allowwrite)
+		recvpack(&c);
+	else if(strcmp(cmd, "git-upload-pack") == 0)
+		servpack(&c);
+	else
+		sysfatal("unsupported command '%s'", cmd);
+	exits(nil);
+}
--- a/util.c
+++ b/util.c
@@ -244,16 +244,15 @@
 	return s;
 }
 
-int
-dprint(char *fmt, ...)
+void
+dprint(int v, char *fmt, ...)
 {
 	va_list ap;
 	int n;
 
-	if(!debug)
-		return 0;
+	if(v >= debug)
+		return;
 	va_start(ap, fmt);
 	n = vfprint(2, fmt, ap);
 	va_end(ap);
-	return n;
 }