~ori/git9

d9f570cacc8e6782fe56a064eabdbb3392d085eb — Ori Bernstein 3 years ago b686227
all: sync from 9front.

major bugfix: lca updates.
14 files changed, 391 insertions(+), 408 deletions(-)

M commit
M common.rc
M compat
M diff
M fs.c
M import
M log.c
M merge
M mkfile
M query.c
M ref.c
M save.c
M serve.c
M util.c
M commit => commit +2 -17
@@ 2,21 2,6 @@
rfork ne
. /sys/lib/git/common.rc

fn whoami{
	name=`{git/conf user.name}
	email=`{git/conf user.email}
	if(test -f /adm/keys.who){
		if(~ $name '')
			name=`{awk -F'|' '$1=="'$user'" {x=$3} END{print x}' </adm/keys.who}
		if(~ $email '')
			email=`{awk -F'|' '$1=="'$user'" {x=$5} END{print x}' </adm/keys.who}
	}
	if(~ $name '')
		name=glenda
	if(~ $email '')
		email=glenda@9front.local
}

fn findbranch{
	branch=`{git/branch}
	if(test -e $gitfs/branch/$branch/tree){


@@ 58,7 43,7 @@ fn editmsg{
			echo '#'
			for(p in $parents)
				echo '# parent:' $p
			git/walk -fAMR $files | subst -g '^' '# '
			git/walk -fAMR $files | subst '^' '# '
			echo '#'
			echo '# Commit message:'
		}


@@ 92,7 77,7 @@ fn commit{
	msg=`''{cat $msgfile}
	if(! ~ $#parents 0)
		pflags='-p'^$parents
	hash=`{git/save -n $"name -e $"email  -m $"msg $pflags $files || die $status}
	hash=`{git/save -n $"name -e $"email -m $"msg $pflags $files || die $status}
	rm -f .git/index9/merge-parents
}


M common.rc => common.rc +4 -4
@@ 39,13 39,13 @@ fn present {
}

fn whoami{
	name=`{git/conf user.name}
	email=`{git/conf user.email}
	name=`$nl{git/conf user.name}
	email=`$nl{git/conf user.email}
	if(test -f /adm/keys.who){
		if(~ $name '')
			name=`{awk -F'|' '$1=="'$user'" {x=$3} END{print x}' </adm/keys.who}
			name=`$nl{awk -F'|' '$1=="'$user'" {x=$3} END{print x}' </adm/keys.who}
		if(~ $email '')
			email=`{awk -F'|' '$1=="'$user'" {x=$5} END{print x}' </adm/keys.who}
			email=`$nl{awk -F'|' '$1=="'$user'" {x=$5} END{print x}' </adm/keys.who}
	}
	if(~ $name '')
		name=glenda

M compat => compat +10 -0
@@ 125,6 125,15 @@ fn cmd_remote{
	}
}

fn cmd_ls-remote{
	if(~ $1 -q)
		shift
	remote=`$nl{git/conf 'remote "'$1'".url'}
	if(~ $#remote 0)
		remote=$1
	git/fetch -l $remote | awk '/^remote/{print $3"\t"$2}'
}

fn cmd_version{
	echo git version 2.2.0
}


@@ 154,5 163,6 @@ if(! test -f '/env/fn#cmd_'$1)
if(! ~ $1 init && ! ~ $1 clone)
	gitroot=`{git/conf -r} || die repo

echo $* >/tmp/gitlog
cmd_$1 $*(2-)
exit ''

M diff => diff +16 -6
@@ 26,12 26,22 @@ fn lsdirty {
		git/query -c $commit HEAD | subst '^..'
}

showed=()
mntgen /mnt/scratch
bind $branch/tree/ /mnt/scratch/a
bind . /mnt/scratch/b
for(f in `$nl{lsdirty | sort | uniq}){
	orig=$branch/tree/$f
	if(! test -f $orig)
		orig=/dev/null
	if(! test -f $f)
		f=/dev/null
	diff -u $orig $f
	if(~ $#showed 0){
		echo diff `{git/query $commit} uncommitted
		showed=1
	}
	cd /mnt/scratch
	a=a/$f
	b=b/$f
	if(! test -f a/$f)
		a=/dev/null
	if(! test -f b/$f)
		b=/dev/null
	diff -u $a $b
}
exit ''

M fs.c => fs.c +38 -30
@@ 12,12 12,13 @@ enum {
	Qhead,
	Qbranch,
	Qcommit,
	Qcommitmsg,
	Qcommitparent,
	Qcommittree,
	Qcommitdata,
	Qcommithash,
	Qcommitauthor,
		Qmsg,
		Qparent,
		Qtree,
		Qcdata,
		Qhash,
		Qauthor,
		Qcommitter,
	Qobject,
	Qctl,
	Qmax,


@@ 284,23 285,23 @@ gcommitgen(int i, Dir *d, void *p)
		d->mode = 0755 | DMDIR;
		d->name = estrdup9p("tree");
		d->qid.type = QTDIR;
		d->qid.path = qpath(c, i, o->id, Qcommittree);
		d->qid.path = qpath(c, i, o->id, Qtree);
		break;
	case 1:
		d->name = estrdup9p("parent");
		d->qid.path = qpath(c, i, o->id, Qcommitparent);
		d->qid.path = qpath(c, i, o->id, Qparent);
		break;
	case 2:
		d->name = estrdup9p("msg");
		d->qid.path = qpath(c, i, o->id, Qcommitmsg);
		d->qid.path = qpath(c, i, o->id, Qmsg);
		break;
	case 3:
		d->name = estrdup9p("hash");
		d->qid.path = qpath(c, i, o->id, Qcommithash);
		d->qid.path = qpath(c, i, o->id, Qhash);
		break;
	case 4:
		d->name = estrdup9p("author");
		d->qid.path = qpath(c, i, o->id, Qcommitauthor);
		d->qid.path = qpath(c, i, o->id, Qauthor);
		break;
	default:
		return -1;


@@ 491,18 492,20 @@ objwalk1(Qid *q, Object *o, Crumb *p, Crumb *c, char *name, vlong qdir, Gitaux *
		q->type = 0;
		c->mtime = o->commit->mtime;
		c->mode = 0644;
		assert(qdir == Qcommit || qdir == Qobject || qdir == Qcommittree || qdir == Qhead);
		assert(qdir == Qcommit || qdir == Qobject || qdir == Qtree || qdir == Qhead || qdir == Qcommitter);
		if(strcmp(name, "msg") == 0)
			q->path = qpath(p, 0, o->id, Qcommitmsg);
			q->path = qpath(p, 0, o->id, Qmsg);
		else if(strcmp(name, "parent") == 0)
			q->path = qpath(p, 1, o->id, Qcommitparent);
			q->path = qpath(p, 1, o->id, Qparent);
		else if(strcmp(name, "hash") == 0)
			q->path = qpath(p, 2, o->id, Qcommithash);
			q->path = qpath(p, 2, o->id, Qhash);
		else if(strcmp(name, "author") == 0)
			q->path = qpath(p, 3, o->id, Qcommitauthor);
			q->path = qpath(p, 3, o->id, Qauthor);
		else if(strcmp(name, "committer") == 0)
			q->path = qpath(p, 3, o->id, Qcommitter);
		else if(strcmp(name, "tree") == 0){
			q->type = QTDIR;
			q->path = qpath(p, 4, o->id, Qcommittree);
			q->path = qpath(p, 4, o->id, Qtree);
			unref(c->obj);
			c->mode = DMDIR | 0755;
			c->obj = readobject(o->commit->tree);


@@ 640,14 643,15 @@ gitwalk1(Fid *fid, char *name, Qid *q)
	case Qcommit:
		e = objwalk1(q, o->obj, o, c, name, Qcommit, aux);
		break;
	case Qcommittree:
		e = objwalk1(q, o->obj, o, c, name, Qcommittree, aux);
	case Qtree:
		e = objwalk1(q, o->obj, o, c, name, Qtree, aux);
		break;
	case Qcommitparent:
	case Qcommitmsg:
	case Qcommitdata:
	case Qcommithash:
	case Qcommitauthor:
	case Qparent:
	case Qmsg:
	case Qcdata:
	case Qhash:
	case Qauthor:
	case Qcommitter:
	case Qctl:
		return Enodir;
	default:


@@ 760,20 764,24 @@ gitread(Req *r)
		else
			dirread9p(r, objgen, aux);
		break;
	case Qcommitmsg:
	case Qmsg:
		readbuf(r, o->commit->msg, o->commit->nmsg);
		break;
	case Qcommitparent:
	case Qparent:
		readcommitparent(r, o);
		break;
	case Qcommithash:
	case Qhash:
		snprint(buf, sizeof(buf), "%H\n", o->hash);
		readstr(r, buf);
		break;
	case Qcommitauthor:
	case Qauthor:
		snprint(buf, sizeof(buf), "%s\n", o->commit->author);
		readstr(r, buf);
		break;
	case Qcommitter:
		snprint(buf, sizeof(buf), "%s\n", o->commit->committer);
		readstr(r, buf);
		break;
	case Qctl:
		e = readctl(r);
		break;


@@ 785,8 793,8 @@ gitread(Req *r)
			objread(r, aux);
		break;
	case Qcommit:
	case Qcommittree:
	case Qcommitdata:
	case Qtree:
	case Qcdata:
		objread(r, aux);
		break;
	default:

M import => import +15 -13
@@ 7,11 7,13 @@ fn sigexit {
	rm -f $diffpath
}


fn apply @{
	git/fs
	email=''
	name=''
	amail=''
	aname=''
	msg=''
	whoami
	parents='-p'^`{git/query HEAD}
	branch=`{git/branch}
	if(test -e $gitfs/branch/$branch/tree)


@@ 26,11 28,11 @@ fn apply @{
	}
	state=="headers" && /^From:/ {
		sub(/^From:[ \t]*/, "", $0);
		name=$0;
		email=$0;
		sub(/[ \t]*<.*$/, "", name);
		sub(/.*</, "", email);
		sub(/>/, "", email);
		aname=$0;
		amail=$0;
		sub(/[ \t]*<.*$/, "", aname);
		sub(/^[^<]*</, "", amail);
		sub(/>[^>]*$/, "", amail);
	}
	state=="headers" && /^Date:/{
		sub(/^Date:[ \t]*/, "", $0)


@@ 45,7 47,7 @@ fn apply @{
		state="body"
		next
	}
	(state=="headers" || state=="body") && (/^diff/ || /^---[ 	]*$/){
	(state=="headers" || state=="body") && (/^diff / || /^---( |$)/){
		state="diff"
	}
	state=="body" {


@@ 57,10 59,10 @@ fn apply @{
	END{
		if(state != "diff")
			exit("malformed patch: " state);
		if(name == "" || email == "" || date == "" || gotmsg == "")
		if(aname == "" || amail == "" || date == "" || gotmsg == "")
			exit("missing headers");
		printf "%s", name > "/env/name"
		printf "%s", email > "/env/email"
		printf "%s", aname > "/env/aname"
		printf "%s", amail > "/env/amail"
		printf "%s", date > "/env/date"
	}
	' || die 'could not import:' $status


@@ 79,8 81,8 @@ fn apply @{
		}
		git/walk -fRMA $files
		if(~ $#nocommit 0){
			hash=`{git/save -n $name -e $email -m $msg -d $date $parents $files}
			echo $hash > $refpath
			if(hash=`{git/save -n $aname -e $amail -N $name -E $email -m $msg -d $date $parents $files})
				echo $hash > $refpath
		}
		status=''''
	'

M log.c => log.c +16 -67
@@ 15,10 15,8 @@ char	*queryexpr;
char	*commitid;
int	shortlog;

Object	**heap;
int	nheap;
int	heapsz;
Objset	done;
Objq	objq;
Pfilt	*pathfilt;

void


@@ 153,6 151,9 @@ show(Object *o)
		tmtime(&tm, o->commit->mtime, tzload("local"));
		Bprint(out, "Hash:\t%H\n", o->hash);
		Bprint(out, "Author:\t%s\n", o->commit->author);
		if(o->commit->committer != nil
		&& strcmp(o->commit->author, o->commit->committer) != 0)
			Bprint(out, "Commiter:\t%s\n", o->commit->committer);
		Bprint(out, "Date:\t%Ï„\n", tmfmt(&tm, "WW MMM D hh:mm:ss z YYYY"));
		Bprint(out, "\n");
		p = o->commit->msg;


@@ 189,64 190,10 @@ showquery(char *q)
}

static void
qput(Object *o)
{
	Object *p;
	int i;

	if(oshas(&done, o->hash))
		return;
	osadd(&done, o);
	if(nheap == heapsz){
		heapsz *= 2;
		heap = earealloc(heap, heapsz, sizeof(Object*));
	}
	heap[nheap++] = o;
	for(i = nheap - 1; i > 0; i = (i-1)/2){
		o = heap[i];
		p = heap[(i-1)/2];
		if(o->commit->mtime < p->commit->mtime)
			break;
		heap[i] = p;
		heap[(i-1)/2] = o;
	}
}

static Object*
qpop(void)
{
	Object *o, *t;
	int i, l, r, m;

	if(nheap == 0)
		return nil;

	i = 0;
	o = heap[0];
	t = heap[--nheap];
	heap[0] = t;
	while(1){
		m = i;
		l = 2*i+1;
		r = 2*i+2;
		if(l < nheap && heap[m]->commit->mtime < heap[l]->commit->mtime)
			m = l;
		if(r < nheap && heap[m]->commit->mtime < heap[r]->commit->mtime)
			m = r;
		else
			break;
		t = heap[m];
		heap[m] = heap[i];
		heap[i] = t;
		i = m;
	}
	return o;
}

static void
showcommits(char *c)
{
	Object *o, *p;
	Qelt e;
	int i;
	Hash h;



@@ 256,18 203,20 @@ showcommits(char *c)
		sysfatal("resolve %s: %r", c);
	if((o = readobject(h)) == nil)
		sysfatal("load %H: %r", h);
	heapsz = 8;
	heap = eamalloc(heapsz, sizeof(Object*));
	qinit(&objq);
	osinit(&done);
	qput(o);
	while((o = qpop()) != nil){
		show(o);
		for(i = 0; i < o->commit->nparent; i++){
			if((p = readobject(o->commit->parent[i])) == nil)
	qput(&objq, o, 0, 0);
	while(qpop(&objq, &e)){
		show(e.o);
		for(i = 0; i < e.o->commit->nparent; i++){
			if(oshas(&done, e.o->commit->parent[i]))
				continue;
			if((p = readobject(e.o->commit->parent[i])) == nil)
				sysfatal("load %H: %r", o->commit->parent[i]);
			qput(p);
			osadd(&done, p);
			qput(&objq, p, 0, 0);
		}
		unref(o);
		unref(e.o);
	}
}


M merge => merge +1 -1
@@ 12,7 12,7 @@ fn merge{
		ours=$ourbr/$f
		base=$basebr/$f
		theirs=$theirbr/$f
		merge1 ./$f $theirs $base $ours
		merge1 ./$f $ours $base $theirs
	}
}


M mkfile => mkfile +6 -0
@@ 51,6 51,12 @@ install:V:
		mk $MKFLAGS $i.install
	for (i in $RC)
		mk $MKFLAGS $i.rcinstall
	cp git.1.man /sys/man/1/git
	cp gitfs.4.man /sys/man/4/gitfs
	cp common.rc /sys/lib/git/common.rc

uninstall:V:
	rm -rf $BIN /sys/lib/git /sys/man/1/git /sys/man/4/gitfs

%.rcinstall:V:
	cp $stem $BIN/$stem

M query.c => query.c +1 -0
@@ 158,6 158,7 @@ main(int argc, char **argv)
	char query[2048], repo[512];

	ARGBEGIN{
	case 'd':	chattygit++;	break;
	case 'p':	fullpath++;	break;
	case 'c':	changes++;	break;
	case 'r':	reverse ^= 1;	break;

M ref.c => ref.c +166 -248
@@ 5,8 5,20 @@
#include "git.h"

typedef struct Eval	Eval;
typedef struct XObject	XObject;
typedef struct Objq	Objq;
typedef struct Lcaq	Lcaq;

struct Lcaq {
	Objq;

	Hash	*head;
	Hash	*tail;
	int	nhead;
	int	ntail;

	Object	*best;
	int	dist;
};


enum {
	Blank,


@@ 22,17 34,10 @@ struct Eval {
	int	stksz;
};

struct XObject {
	Object	*obj;
	Object	*mark;
	XObject	*queue;
	XObject	*next;
};

struct Objq {
	Objq	*next;
	Object	*o;
	int	color;
static char *colors[] = {
[Keep] "keep",
[Drop] "drop",
[Blank] "blank",
};

static Object zcommit = {


@@ 128,213 133,98 @@ take(Eval *ev, char *m)
	return 1;
}

XObject*
hnode(XObject *ht[], Object *o)
{
	XObject *h;
	int	hh;

	hh = o->hash.h[0] & 0xff;
	for(h = ht[hh]; h; h = h->next)
		if(hasheq(&o->hash, &h->obj->hash))
			return h;

	h = emalloc(sizeof(*h));
	h->obj = o;
	h->mark = nil;
	h->queue = nil;
	h->next = ht[hh];
	ht[hh] = h;
	return h;
}

Object*
ancestor(Object *a, Object *b)
static int
pickbest(Lcaq *q, Qelt *e, int color)
{
	Object *o, *p, *r;
	XObject *ht[256];
	XObject *h, *q, *q1, *q2;
	int i;

	if(a == b)
		return a;
	if(a == nil || b == nil)
		return nil;
	r = nil;
	memset(ht, 0, sizeof(ht));
	q1 = nil;

	h = hnode(ht, a);
	h->mark = a;
	h->queue = q1;
	q1 = h;
	int i, best, exact;

	h = hnode(ht, b);
	h->mark = b;
	h->queue = q1;
	q1 = h;

	while(1){
		q2 = nil;
		while(q = q1){
			q1 = q->queue;
			q->queue = nil;
			o = q->obj;
			for(i = 0; i < o->commit->nparent; i++){
				p = readobject(o->commit->parent[i]);
				if(p == nil)
					goto err;
				h = hnode(ht, p);
				if(h->mark != nil){
					if(h->mark != q->mark){
						r = h->obj;
						goto done;
					}
				} else {
					h->mark = q->mark;
					h->queue = q2;
					q2 = h;
				}
			}
		}
		if(q2 == nil){
err:
			werrstr("no common ancestor");
			break;
		}
		q1 = q2;
	best = 0;
	exact = 0;
	if(color == Blank || e->color == color)
		return 0;
	if(e->dist < q->dist){
		dprint(1, "found best (dist %d < %d): %H\n", e->dist, q->dist, e->o->hash);
		best = 1;
	}
done:
	for(i=0; i<nelem(ht); i++){
		while(h = ht[i]){
			ht[i] = h->next;
			free(h);
	for(i = 0; i < q->nhead; i++)
		if(hasheq(&q->head[i], &e->o->hash)){
			dprint(1, "found best (exact head): %H\n", e->o->hash);
			best = 1;
			exact = 1;
		}
	for(i = 0; i < q->ntail; i++)
		if(hasheq(&q->tail[i], &e->o->hash)){
			dprint(1, "found best (exact tail): %H\n", e->o->hash);
			best = 1;
			exact = 1;
		}
	if(best){
		q->best = e->o;
		q->dist = e->dist;
	}
	return r;
}

int
lca(Eval *ev)
{
	Object *a, *b, *o;

	if(ev->nstk < 2){
		werrstr("ancestor needs 2 objects");
		return -1;
	}
	a = pop(ev);
	b = pop(ev);
	o = ancestor(a, b);
	if(o == nil)
		return -1;
	push(ev, o);
	return 0;
	return exact;
}

int
parentof(Eval *ev)
static int
repaint(Lcaq *lcaq, Objset *keep, Objset *drop, Object *o, int dist, int ancestor)
{
	Objq *q, *n, *e;
	Object *p, *c;
	Objset seen;
	int i, r;
	Lcaq objq;
	Qelt e;
	Object *p;
	int i;

	print("parentof\n");
	if(ev->nstk < 2){
		werrstr("parentof needs 2 objects");
	qinit(&objq);
	if((o = readobject(o->hash)) == nil)
		return -1;
	}
	osinit(&seen);
	r = 0;
	p = pop(ev);
	c = pop(ev);
	q = emalloc(sizeof(Objq));
	q->o = ref(p);
	e = q;
	if(p->type != GCommit || c->type != GCommit){
		werrstr("object is not commit");
		return -1;
	}
	while(q != nil){
		if(oshas(&seen, q->o->hash))
			goto next; 
		osadd(&seen, q->o);
		if(hasheq(&q->o->hash, &c->hash)){
			push(ev, c);
	qput(&objq, o, Drop, dist);
	while(qpop(&objq, &e)){
		o = e.o;
		if(oshas(drop, o->hash))
			continue;
		if(ancestor && pickbest(lcaq, &e, Keep))
			goto out;
		if(!oshas(keep, o->hash)){
			dprint(2, "repaint: blank => drop %H\n", o->hash);
			osadd(drop, o);
			continue;
		}
		for(i = 0; i < q->o->commit->nparent; i++){
			if((c = readobject(q->o->commit->parent[i])) == nil){
				r = -1;
				goto out;
			}
			if(c->type != GCommit){
				fprint(2, "warning: %H does not point at commit\n", c->hash);
				unref(c);
		for(i = 0; i < o->commit->nparent; i++){
			if(oshas(drop, o->commit->parent[i]))
				continue;
			if((p = readobject(o->commit->parent[i])) == nil)
				goto out;
			if(p->type != GCommit){
				fprint(2, "hash %H not commit\n", p->hash);
				unref(p);
			}
			n = emalloc(sizeof(Objq));
			n->next = nil;
			n->o = c;
			e->next = n;
			e = n;
			unref(c);
			qput(&objq, p, Drop, e.dist+1);
		}
next:
		n = q->next;
		free(q);
		q = n;
		unref(e.o);
	}
out:
	osclear(&seen);
	for(; q != nil; q = n) {
		n = q->next;
		free(q);
	}
	return r;
}

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);
	}
	qclear(&objq);
	return 0;
}

int
findtwixt(Hash *head, int nhead, Hash *tail, int ntail, Object ***res, int *nres)
static int
paint(Hash *head, int nhead, Hash *tail, int ntail, Object ***res, int *nres, int ancestor)
{
	Objq *q, *e, *n, **p;
	Qelt e;
	Lcaq objq;
	Objset keep, drop;
	Object *o, *c;
	int i, ncolor;

	e = nil;
	q = nil;
	p = &q;
	osinit(&keep);
	osinit(&drop);
	qinit(&objq);
	objq.head = head;
	objq.nhead = nhead;
	objq.tail = tail;
	objq.ntail = ntail;
	objq.dist = 1<<30;

	for(i = 0; i < nhead; i++){
		if(hasheq(&head[i], &Zhash))
			continue;
		if((o = readobject(head[i])) == nil){
			fprint(2, "warning: %H does not point at commit\n", o->hash);
			werrstr("read head %H: %r", head[i]);


@@ 345,17 235,11 @@ findtwixt(Hash *head, int nhead, Hash *tail, int ntail, Object ***res, int *nres
			unref(o);
			continue;
		}
		dprint(1, "twixt init: keep %H\n", o->hash);
		e = emalloc(sizeof(Objq));
		e->o = o;
		e->color = Keep;
		*p = e;
		p = &e->next;
		dprint(1, "init: keep %H\n", o->hash);
		qput(&objq, o, Keep, 0);
		unref(o);
	}		
	for(i = 0; i < ntail; i++){
		if(hasheq(&tail[i], &Zhash))
			continue;
		if((o = readobject(tail[i])) == nil){
			werrstr("read tail %H: %r", tail[i]);
			return -1;


@@ 366,78 250,117 @@ findtwixt(Hash *head, int nhead, Hash *tail, int ntail, Object ***res, int *nres
			continue;
		}
		dprint(1, "init: drop %H\n", o->hash);
		e = emalloc(sizeof(Objq));
		e->o = o;
		e->color = Drop;
		*p = e;
		p = &e->next;
		qput(&objq, o, Drop, 0);
		unref(o);
	}

	dprint(1, "finding twixt commits\n");
	while(q != nil){
		if(oshas(&drop, q->o->hash))
	while(qpop(&objq, &e)){
		if(oshas(&drop, e.o->hash))
			ncolor = Drop;
		else if(oshas(&keep, q->o->hash))
		else if(oshas(&keep, e.o->hash))
			ncolor = Keep;
		else
			ncolor = Blank;
		if(ncolor == Drop || ncolor == Keep && q->color == Keep)
			goto next;
		if(ncolor == Keep && q->color == Drop){
			if(repaint(&keep, &drop, q->o) == -1)
		if(ancestor && pickbest(&objq, &e, ncolor))
			goto exactlca;
		if(ncolor == Keep && e.color == Keep || ncolor == Drop)
			continue;
		if(ncolor == Keep && e.color == Drop){
			if(repaint(&objq, &keep, &drop, e.o, e.dist, ancestor) == -1)
				goto error;
		}else if (ncolor == Blank) {
			dprint(2, "visit: %s %H\n", q->color == Keep ? "keep" : "drop", q->o->hash);
			if(q->color == Keep)
				osadd(&keep, q->o);
			if(e.color == Keep)
				osadd(&keep, e.o);
			else
				osadd(&drop, q->o);
			for(i = 0; i < q->o->commit->nparent; i++){
				if((c = readobject(q->o->commit->parent[i])) == nil)
				osadd(&drop, e.o);
			o = readobject(e.o->hash);
			for(i = 0; i < o->commit->nparent; i++){
				if((c = readobject(e.o->commit->parent[i])) == nil)
					goto error;
				if(c->type != GCommit){
					fprint(2, "warning: %H does not point at commit\n", c->hash);
					unref(c);
					continue;
				}
				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;
				dprint(2, "\tenqueue: %s %H\n", colors[e.color], c->hash);
				qput(&objq, c, e.color, e.dist+1);
				unref(c);
			}
		}else{
			sysfatal("oops");
			unref(o);
		}
next:
		n = q->next;
		unref(q->o);
		free(q);
		q = n;
	}
	*res = eamalloc(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)++;
exactlca:
	if(ancestor){
		dprint(1, "found ancestor\n");
		if(objq.best == nil){
			*nres = 0;
			*res = nil;
		}else{
			*nres = 1;
			*res = eamalloc(1, sizeof(Object*));
			(*res)[0] = objq.best;
		}
	}else{
		dprint(1, "found twixt\n");
		*res = eamalloc(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);
	}
	dprint(1, "twixt error: %r\n");
	free(objq.heap);
	return -1;
}

int
findtwixt(Hash *head, int nhead, Hash *tail, int ntail, Object ***res, int *nres)
{
	return paint(head, nhead, tail, ntail, res, nres, 0);
}

Object*
ancestor(Object *a, Object *b)
{
	Object **o, *r;
	int n;

	if(paint(&a->hash, 1, &b->hash, 1, &o, &n, 1) == -1 || n == 0)
		return nil;
	r = o[0];
	free(o);
	return ref(r);
}

int
lca(Eval *ev)
{
	Object *a, *b, **o;
	int n;

	if(ev->nstk < 2){
		werrstr("ancestor needs 2 objects");
		return -1;
	}
	n = 0;
	b = pop(ev);
	a = pop(ev);
	paint(&a->hash, 1, &b->hash, 1, &o, &n, 1);
	if(n == 0)
		return -1;
	push(ev, *o);
	free(o);
	return 0;
}

static int
parent(Eval *ev)
{


@@ 609,18 532,13 @@ evalpostfix(Eval *ev)

	while(1){
		eatspace(ev);
		switch(*ev->p){
		switch(ev->p[0]){
		case '^':
		case '~':
			ev->p++;
			if(parent(ev) == -1)
				return -1;
			break;
		case '<':
			ev->p++;
			if(parentof(ev) == -1)
				return -1;
			break;
		case '@':
			ev->p++;
			if(lca(ev) == -1)

M save.c => save.c +44 -21
@@ 14,6 14,14 @@ enum {
	Maxparents = 16,
};

char	*authorname;
char	*authoremail;
char	*committername;
char	*committeremail;
char	*commitmsg;
Hash	parents[Maxparents];
int	nparents;

int
gitmode(Dirent *e)
{


@@ 299,7 307,7 @@ err:


void
mkcommit(Hash *c, char *msg, char *name, char *email, vlong date, Hash *parents, int nparents, Hash tree)
mkcommit(Hash *c, vlong date, Hash tree)
{
	char *s, h[64];
	int ns, nh, i;


@@ 309,10 317,10 @@ mkcommit(Hash *c, char *msg, char *name, char *email, vlong date, Hash *parents,
	fmtprint(&f, "tree %H\n", tree);
	for(i = 0; i < nparents; i++)
		fmtprint(&f, "parent %H\n", parents[i]);
	fmtprint(&f, "author %s <%s> %lld +0000\n", name, email, date);
	fmtprint(&f, "committer %s <%s> %lld +0000\n", name, email, date);
	fmtprint(&f, "author %s <%s> %lld +0000\n", authorname, authoremail, date);
	fmtprint(&f, "committer %s <%s> %lld +0000\n", committername, committeremail, date);
	fmtprint(&f, "\n");
	fmtprint(&f, "%s", msg);
	fmtprint(&f, "%s", commitmsg);
	s = fmtstrflush(&f);

	ns = strlen(s);


@@ 346,9 354,9 @@ usage(void)
void
main(int argc, char **argv)
{
	Hash th, ch, parents[Maxparents];
	char *msg, *name, *email, *dstr, cwd[1024];
	int i, r, ncwd, nparents;
	Hash th, ch;
	char *dstr, cwd[1024];
	int i, r, ncwd;
	vlong date;
	Object *t;



@@ 357,19 365,29 @@ main(int argc, char **argv)
		sysfatal("could not find git repo: %r");
	if(getwd(cwd, sizeof(cwd)) == nil)
		sysfatal("getcwd: %r");
	msg = nil;
	name = nil;
	email = nil;
	dstr = nil;
	date = time(nil);
	nparents = 0;
	ncwd = strlen(cwd);

	ARGBEGIN{
	case 'm':	msg = EARGF(usage());	break;
	case 'n':	name = EARGF(usage());	break;
	case 'e':	email = EARGF(usage());	break;
	case 'd':	dstr = EARGF(usage());	break;
	case 'm':
		commitmsg = EARGF(usage());
		break;
	case 'n':
		authorname = EARGF(usage());
		break;
	case 'e':
		authoremail = EARGF(usage());
		break;
	case 'N':
		committername = EARGF(usage());
		break;
	case 'E':
		committeremail = EARGF(usage());
		break;
	case 'd':
		dstr = EARGF(usage());
		break;
	case 'p':
		if(nparents >= Maxparents)
			sysfatal("too many parents");


@@ 378,21 396,26 @@ main(int argc, char **argv)
		break;
	default:
		usage();
		break;
	}ARGEND;

	if(!msg)
	if(commitmsg == nil)
		sysfatal("missing message");
	if(!name)
	if(authorname == nil)
		sysfatal("missing name");
	if(!email)
	if(authoremail == nil)
		sysfatal("missing email");
	if((committername == nil) != (committeremail == nil))
		sysfatal("partially specified committer");
	if(committername == nil && committeremail == nil){
		committername = authorname;
		committeremail = authoremail;
	}
	if(dstr){
		date=strtoll(dstr, &dstr, 10);
		if(strlen(dstr) != 0)
			sysfatal("could not parse date %s", dstr);
	}
	if(msg == nil || name == nil)
		usage();
	for(i = 0; i < argc; i++){
		cleanname(argv[i]);
		if(*argv[i] == '/' && strncmp(argv[i], cwd, ncwd) == 0)


@@ 405,7 428,7 @@ main(int argc, char **argv)
	r = treeify(t, argv, argv + argc, 0, &th);
	if(r == -1)
		sysfatal("could not commit: %r\n");
	mkcommit(&ch, msg, name, email, date, parents, nparents, th);
	mkcommit(&ch, date, th);
	print("%H\n", ch);
	exits(nil);
}

M serve.c => serve.c +1 -1
@@ 34,7 34,7 @@ showrefs(Conn *c)
	refs = nil;
	names = nil;
	if(resolveref(&head, "HEAD") != -1)
		if(fmtpkt(c, "%H HEAD", head) == -1)
		if(fmtpkt(c, "%H HEAD\n", head) == -1)
			goto error;

	if((nrefs = listrefs(&refs, &names)) == -1)

M util.c => util.c +71 -0
@@ 321,3 321,74 @@ showprogress(int x, int pct)
	}
	return pct;
}

void
qinit(Objq *q)
{
	memset(q, 0, sizeof(Objq));
	q->nheap = 0;
	q->heapsz = 8;
	q->heap = eamalloc(q->heapsz, sizeof(Qelt));
}

void
qclear(Objq *q)
{
	free(q->heap);
}

void
qput(Objq *q, Object *o, int color, int dist)
{
	Qelt t;
	int i;

	if(q->nheap == q->heapsz){
		q->heapsz *= 2;
		q->heap = earealloc(q->heap, q->heapsz, sizeof(Qelt));
	}
	q->heap[q->nheap].o = o;
	q->heap[q->nheap].color = color;
	q->heap[q->nheap].dist = dist;
	q->heap[q->nheap].mtime = o->commit->mtime;
	for(i = q->nheap; i > 0; i = (i-1)/2){
		if(q->heap[i].mtime < q->heap[(i-1)/2].mtime)
			break;
		t = q->heap[i];
		q->heap[i] = q->heap[(i-1)/2];
		q->heap[(i-1)/2] = t;
	}
	q->nheap++;
}

int
qpop(Objq *q, Qelt *e)
{
	int i, l, r, m;
	Qelt t;

	if(q->nheap == 0)
		return 0;
	*e = q->heap[0];
	if(--q->nheap == 0)
		return 1;

	i = 0;
	q->heap[0] = q->heap[q->nheap];
	while(1){
		m = i;
		l = 2*i+1;
		r = 2*i+2;
		if(l < q->nheap && q->heap[m].mtime < q->heap[l].mtime)
			m = l;
		if(r < q->nheap && q->heap[m].mtime < q->heap[r].mtime)
			m = r;
		if(m == i)
			break;
		t = q->heap[m];
		q->heap[m] = q->heap[i];
		q->heap[i] = t;
		i = m;
	}
	return 1;
}