~ori/git9

6ea2d8eede5056a0f319af9c5f47ed3959d08045 — Ori Bernstein 2 years ago eebc340
sync
20 files changed, 220 insertions(+), 251 deletions(-)

M add -rw-r--r-- => -rwxr-xr-x
M branch
M clone
M commit
M compat
M fs.c
R fetch.c => get.c
M git.h
M import
M log.c
M mkfile
M pack.c
M proto.c
M pull
M ref.c
M revert
M send.c
M serve.c
M util.c
M walk.c
M add => add +0 -0
M branch => branch +9 -9
@@ 48,9 48,12 @@ if(~ $#newbr 0){
modified=`$nl{git/query -c HEAD $base | grep '^[^-]' | subst '^..'}
deleted=`$nl{git/query -c HEAD $base | grep '^-' | subst '^..'}

if(! ~ $#modified 0 || ! ~ $#deleted 0 && ~ $#merge 0){
	git/walk -fRMA $modified $deleted || 
		die 'uncommited changes would be clobbered'
# if we're not merging, don't clobber existing changes.
if(~ $#merge 0){
	if(! ~ $#modified 0 || ! ~ $#deleted 0){
		git/walk -fRMA $modified $deleted || 
			die 'uncommitted changes would be clobbered'
	}
}
if(~ $delete 1){
	rm -f .git/$new


@@ 68,8 71,6 @@ basedir=`{git/query -p $base}
dirtypaths=()
if(! ~ $#modified 0 || ! ~ $#deleted 0)
	dirtypaths=`$nl{git/walk -cfRMA $modified $deleted}
if(! ~ $#modified 0 || ! ~ $#deleted 0)
	dirtypaths=`$nl{git/walk -cfRMA $modified $deleted}
if(~ $#dirtypaths 0)
	cleanpaths=($modified $deleted)
if not {


@@ 99,10 100,9 @@ for(m in $cleanpaths){
		rm -rf .git/index9/tracked/$m
	}
	if(~ $b file){
		if(cp -x -- $basedir/tree/$m $m)
			walk -eq $m > .git/index9/tracked/$m
		if not
			echo -n > .git/index9/tracked/$m
		cp -x -- $basedir/tree/$m $m
		walk -eq $m > .git/index9/tracked/$m
		touch $m
	}
}


M clone => clone +1 -1
@@ 33,7 33,7 @@ fn clone{
		echo '[remote "origin"]'
		echo '	url='$remote
	}
	{git/fetch  $debug $branchflag $remote >[2=3] | awk '
	{git/get  $debug $branchflag $remote >[2=3] | awk '
		BEGIN{
			headref=""
			if(ENVIRON["branch"] != "")

M commit => commit +2 -1
@@ 101,7 101,8 @@ fn update{
}

fn sigexit{
	rm -f $msgfile $msgfile.tmp
	if(~ ! $#msgfile 0)
		rm -f $msgfile $msgfile.tmp
}

gitup

M compat => compat +1 -1
@@ 131,7 131,7 @@ fn cmd_ls-remote{
	remote=`$nl{git/conf 'remote "'$1'".url'}
	if(~ $#remote 0)
		remote=$1
	git/fetch -l $remote | awk '/^remote/{print $3"\t"$2}'
	git/get -l $remote | awk '/^remote/{print $3"\t"$2}'
}

fn cmd_version{

M fs.c => fs.c +45 -14
@@ 70,13 70,14 @@ char *qroot[] = {
	"ctl",
};

#define Eperm	"permission denied";
#define Eexist	"does not exist";
#define E2long	"path too long";
#define Enodir	"not a directory";
#define Erepo	"unable to read repo";
#define Egreg	"wat";
#define Ebadobj	"invalid object";
#define Eperm	"permission denied"
#define Eexist	"does not exist"
#define E2long	"path too long"
#define Enodir	"not a directory"
#define Erepo	"unable to read repo"
#define Eobject "invalid object"
#define Egreg	"wat"
#define Ebadobj	"invalid object"

char	gitdir[512];
char	*username;


@@ 377,19 378,20 @@ objread(Req *r, Gitaux *aux)
static void
readcommitparent(Req *r, Object *o)
{
	char *buf, *p;
	char *buf, *p, *e;
	int i, n;

	n = o->commit->nparent * (40 + 2);
	/* 40 bytes per hash, 1 per nl, 1 for terminator */
	n = o->commit->nparent * (40 + 1) + 1;
	buf = emalloc(n);
	p = buf;
	e = buf + n;
	for (i = 0; i < o->commit->nparent; i++)
		p += sprint(p, "%H\n", o->commit->parent[i]);
	readbuf(r, buf, n);
		p = seprint(p, e, "%H\n", o->commit->parent[i]);
	readbuf(r, buf, p - buf);
	free(buf);
}


static void
gitattach(Req *r)
{


@@ 623,9 625,9 @@ gitwalk1(Fid *fid, char *name, Qid *q)
			e = objwalk1(q, o->obj, o, c, name, Qobject, aux);
		}else{
			if(hparse(&h, name) == -1)
				return "invalid object name";
				return Eobject;
			if((c->obj = readobject(h)) == nil)
				return "could not read object";
				return Eobject;
			if(c->obj->type == GBlob || c->obj->type == GTag){
				c->mode = 0644;
				q->type = 0;


@@ 804,6 806,34 @@ gitread(Req *r)
}

static void
gitopen(Req *r)
{
	Gitaux *aux;
	Crumb *c;

	aux = r->fid->aux;
	c = crumb(aux, 0);
	switch(r->ifcall.mode&3){
	default:
		respond(r, "botched mode");
		break;
	case OWRITE:
		respond(r, Eperm);
		break;
	case OREAD:
	case ORDWR:
		respond(r, nil);
		break;
	case OEXEC:
		if((c->mode & 0111) == 0)
			respond(r, Eperm);
		else
			respond(r, nil);
		break;
	}
}

static void
gitstat(Req *r)
{
	Gitaux *aux;


@@ 829,6 859,7 @@ Srv gitsrv = {
	.attach=gitattach,
	.walk1=gitwalk1,
	.clone=gitclone,
	.open=gitopen,
	.read=gitread,
	.stat=gitstat,
	.destroyfid=gitdestroyfid,

R fetch.c => get.c +34 -6
@@ 180,12 180,13 @@ fail(char *pack, char *idx, char *msg, ...)
int
fetchpack(Conn *c)
{
	char buf[Pktmax], *sp[3];
	char buf[Pktmax], *sp[3], *ep;
	char *packtmp, *idxtmp, **ref;
	Hash h, *have, *want;
	int nref, refsz, first;
	int i, n, req, pfd;
	int i, n, l, req, pfd;
	vlong packsz;
	Objset hadobj;
	Object *o;

	nref = 0;


@@ 246,13 247,19 @@ fetchpack(Conn *c)
		req = 1;
	}
	flushpkt(c);
	osinit(&hadobj);
	for(i = 0; i < nref; i++){
		if(hasheq(&have[i], &Zhash))
		if(hasheq(&have[i], &Zhash) || oshas(&hadobj, have[i]))
			continue;
		if((o = readobject(have[i])) == nil)
			sysfatal("missing object we should have: %H", have[i]);
		osadd(&hadobj, o);
		unref(o);	
		n = snprint(buf, sizeof(buf), "have %H\n", have[i]);
		if(writepkt(c, buf, n + 1) == -1)
			sysfatal("could not send have for %H", have[i]);
	}
	osclear(&hadobj);
	if(!req)
		flushpkt(c);



@@ 260,7 267,7 @@ fetchpack(Conn *c)
	if(writepkt(c, buf, n) == -1)
		sysfatal("write: %r");
	if(!req)
		return 0;
		goto showrefs;
	if(readphase(c) == -1)
		sysfatal("read: %r");
	if((n = readpkt(c, buf, sizeof(buf))) == -1)


@@ 277,9 284,30 @@ fetchpack(Conn *c)
		sysfatal("could not create %s: %r", packtmp);

	fprint(2, "fetching...\n");
	packsz = 0;
	/*
	 * Work around torvalds git bug: we get duplicate have lines
	 * somtimes, even though the protocol is supposed to start the
	 * pack file immediately.
	 *
	 * Skip ahead until we read 'PACK' off the wire
	 */
	while(1){
		if(readn(c->rfd, buf, 4) != 4)
			sysfatal("fetch packfile: short read");
		buf[4] = 0;
		if(strncmp(buf, "PACK", 4) == 0)
			break;
		l = strtol(buf, &ep, 16);
		if(l == 0 || ep != buf + 4)
			sysfatal("fetch packfile: junk pktline");
		if(readn(c->rfd, buf, l) != l)
			sysfatal("fetch packfile: short read");
	}
	if(write(pfd, "PACK", 4) != 4)
		sysfatal("write pack header: %r");
	packsz = 4;
	while(1){
		n = readn(c->rfd, buf, sizeof buf);
		n = read(c->rfd, buf, sizeof buf);
		if(n == 0)
			break;
		if(n == -1 || write(pfd, buf, n) != n)

M git.h => git.h +6 -5
@@ 4,6 4,7 @@
#include <flate.h>
#include <regexp.h>

typedef struct Capset	Capset;
typedef struct Conn	Conn;
typedef struct Hash	Hash;
typedef struct Delta	Delta;


@@ 26,6 27,8 @@ enum {
	Npackcache	= 32,
	Hashsz		= 20,
	Pktmax		= 65536,
	KiB		= 1024,
	MiB		= 1024*KiB,
};

enum {


@@ 155,16 158,14 @@ struct Objset {

struct Qelt {
	Object	*o;
	vlong	mtime;
	vlong	ctime;
	int	color;
	int	dist;
};

struct Objq {
	Qelt	*heap;
	int	nheap;
	int	heapsz;
	int	nkeep;
};

struct Dtab {


@@ 241,9 242,9 @@ struct Delta {

extern Reprog	*authorpat;
extern Objset	objcache;
extern vlong	cachemax;
extern Hash	Zhash;
extern int	chattygit;
extern int	cachemax;
extern int	interactive;

#pragma varargck type "H" Hash


@@ 321,5 322,5 @@ void	closeconn(Conn *);
/* queues */
void	qinit(Objq*);
void	qclear(Objq*);
void	qput(Objq*, Object*, int, int);
void	qput(Objq*, Object*, int);
int	qpop(Objq*, Qelt*);

M import => import +8 -1
@@ 45,12 45,19 @@ fn apply @{
	}
	state=="headers" && /^$/ {
		state="body"
		next
	}
	(state=="headers" || state=="body") && (/^diff / || /^---( |$)/){
		state="diff"
	}
	state=="body" && /^[ 	]*$/ {
		empty=1
		next
	}
	state=="body" {
		if(empty)
			printf "\n" > "/env/msg"
		empty=0
		sub(/[ 	]+$/, "")
		print > "/env/msg"
	}
	state=="diff" {

M log.c => log.c +3 -3
@@ 153,7 153,7 @@ show(Object *o)
		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, "Committer:\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;


@@ 205,7 205,7 @@ showcommits(char *c)
		sysfatal("load %H: %r", h);
	qinit(&objq);
	osinit(&done);
	qput(&objq, o, 0, 0);
	qput(&objq, o, 0);
	while(qpop(&objq, &e)){
		show(e.o);
		for(i = 0; i < e.o->commit->nparent; i++){


@@ 214,7 214,7 @@ showcommits(char *c)
			if((p = readobject(e.o->commit->parent[i])) == nil)
				sysfatal("load %H: %r", o->commit->parent[i]);
			osadd(&done, p);
			qput(&objq, p, 0, 0);
			qput(&objq, p, 0);
		}
		unref(e.o);
	}

M mkfile => mkfile +1 -7
@@ 3,7 3,7 @@
BIN=/$objtype/bin/git
TARG=\
	conf\
	fetch\
	get\
	fs\
	log\
	query\


@@ 51,12 51,6 @@ 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 pack.c => pack.c +4 -5
@@ 65,8 65,8 @@ static Object	*readidxobject(Biobuf *, Hash, int);
Objset objcache;
Object *lruhead;
Object *lrutail;
int	ncache;
int	cachemax = 4096;
vlong	ncache;
vlong	cachemax = 512*MiB;
Packf	*packf;
int	npackf;
int	openpacks;


@@ 158,7 158,7 @@ cache(Object *o)
	if(!(o->flag & Ccache)){
		o->flag |= Ccache;
		ref(o);
		ncache++;
		ncache += o->size;
	}
	while(ncache > cachemax && lrutail != nil){
		p = lrutail;


@@ 168,8 168,8 @@ cache(Object *o)
		p->flag &= ~Ccache;
		p->prev = nil;
		p->next = nil;
		ncache -= p->size;
		unref(p);
		ncache--;
	}		
}



@@ 1036,7 1036,6 @@ retry:
			return obj;
		}
	}
			

	snprint(hbuf, sizeof(hbuf), "%H", h);
	snprint(path, sizeof(path), ".git/objects/%c%c/%s", hbuf[0], hbuf[1], hbuf + 2);

M proto.c => proto.c +15 -2
@@ 220,14 220,27 @@ static int
issmarthttp(Conn *c, char *direction)
{
	char buf[Pktmax+1], svc[128];
	int n;
	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("dumb http protocol not supported");
		werrstr("invalid initial packet line");
		return -1;
	}
	if(readpkt(c, buf, sizeof(buf)) != 0){

M pull => pull +1 -4
@@ 7,13 7,10 @@ fn update{
	upstream=$2
	url=$3
	dir=$4
	bflag=()
	dflag=()
	if(! ~ $#branch 0)
		bflag=(-b $branch)
	if(! ~ $#debug 0)
		dflag='-d'
	{git/fetch $dflag $bflag -u $upstream $url >[2=3] || die $status} | awk '
	{git/get $dflag -u $upstream $url >[2=3] || die $status} | awk '
	/^remote/{
		if($2=="HEAD")
			next

M ref.c => ref.c +57 -155
@@ 5,25 5,12 @@
#include "git.h"

typedef struct Eval	Eval;
typedef struct Lcaq	Lcaq;

struct Lcaq {
	Objq;

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

	Object	*best;
	int	dist;
};


enum {
	Blank,
	Keep,
	Drop,
	Skip,
};

struct Eval {


@@ 38,6 25,7 @@ static char *colors[] = {
[Keep] "keep",
[Drop] "drop",
[Blank] "blank",
[Skip] "skip",
};

static Object zcommit = {


@@ 51,26 39,6 @@ eatspace(Eval *ev)
		ev->p++;
}

int
objdatecmp(void *pa, void *pb)
{
	Object *a, *b;
	int r;

	a = readobject((*(Object**)pa)->hash);
	b = readobject((*(Object**)pb)->hash);
	assert(a->type == GCommit && b->type == GCommit);
	if(a->commit->mtime == b->commit->mtime)
		r = 0;
	else if(a->commit->mtime < b->commit->mtime)
		r = -1;
	else
		r = 1;
	unref(a);
	unref(b);
	return r;
}

void
push(Eval *ev, Object *o)
{


@@ 134,96 102,19 @@ take(Eval *ev, char *m)
}

static int
pickbest(Lcaq *q, Qelt *e, int color)
{
	int i, best, exact;

	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;
	}
	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 exact;
}

static int
repaint(Lcaq *lcaq, Objset *keep, Objset *drop, Object *o, int dist, int ancestor)
{
	Lcaq objq;
	Qelt e;
	Object *p;
	int i;

	qinit(&objq);
	if((o = readobject(o->hash)) == nil)
		return -1;
	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 < 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);
			}
			qput(&objq, p, Drop, e.dist+1);
		}
		unref(e.o);
	}
out:
	qclear(&objq);
	return 0;
}

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

	osinit(&keep);
	osinit(&drop);
	memset(&objq, 0, sizeof(objq));
	osinit(&skip);
	qinit(&objq);
	objq.head = head;
	objq.nhead = nhead;
	objq.tail = tail;
	objq.ntail = ntail;
	objq.dist = 1<<30;
	nskip = 0;

	for(i = 0; i < nhead; i++){
		if((o = readobject(head[i])) == nil){


@@ 237,7 128,7 @@ paint(Hash *head, int nhead, Hash *tail, int ntail, Object ***res, int *nres, in
			continue;
		}
		dprint(1, "init: keep %H\n", o->hash);
		qput(&objq, o, Keep, 0);
		qput(&objq, o, Keep);
		unref(o);
	}		
	for(i = 0; i < ntail; i++){


@@ 251,70 142,83 @@ paint(Hash *head, int nhead, Hash *tail, int ntail, Object ***res, int *nres, in
			continue;
		}
		dprint(1, "init: drop %H\n", o->hash);
		qput(&objq, o, Drop, 0);
		qput(&objq, o, Drop);
		unref(o);
	}

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


@@ 406,7 310,7 @@ static int
range(Eval *ev)
{
	Object *a, *b, *p, *q, **all;
	int nall, *idx, mark;
	int nall, *idx;
	Objset keep, skip;

	b = pop(ev);


@@ 424,7 328,6 @@ range(Eval *ev)
	all = nil;
	idx = nil;
	nall = 0;
	mark = ev->nstk;
	osinit(&keep);
	osinit(&skip);
	osadd(&keep, a);


@@ 459,7 362,6 @@ range(Eval *ev)
		nall++;
	}
	free(all);
	qsort(ev->stk + mark, ev->nstk - mark, sizeof(Object*), objdatecmp);
	return 0;
error:
	free(all);

M revert => revert +3 -1
@@ 5,7 5,8 @@ rfork en
gitup

flagfmt='c:query query' args='file ...'
eval `''{aux/getflags $*} || exec aux/usage
if (! eval `''{aux/getflags $*} || ~ $#* 0)
	exec aux/usage

commit=$gitfs/HEAD
if(~ $#query 1)


@@ 15,6 16,7 @@ files=`$nl{cleanname -d $gitrel $* | drop $gitroot}
for(f in `$nl{cd $commit/tree/ && walk -f ./$files}){
	mkdir -p `{basename -d $f}
	cp -x -- $commit/tree/$f $f
	touch $f
	git/add $f
}
exit ''

M send.c => send.c +10 -4
@@ 134,8 134,8 @@ sendpack(Conn *c)
	nmap = nours;
	map = eamalloc(nmap, sizeof(Map));
	for(i = 0; i < nmap; i++){
		map[i].ours = ours[i];
		map[i].theirs = Zhash;
		map[i].ours = ours[i];
		map[i].ref = refs[i];
	}
	while(1){


@@ 155,9 155,15 @@ sendpack(Conn *c)
		theirs = earealloc(theirs, ntheirs+1, sizeof(Hash));
		if(hparse(&theirs[ntheirs], sp[0]) == -1)
			sysfatal("invalid hash %s", sp[0]);
		if((idx = findkey(map, nmap, sp[1])) != -1)
			map[idx].theirs = theirs[ntheirs];
		/*
		 * we only keep their ref if we can read the object to add it
		 * to our reachability; otherwise, discard it; we only care
		 * that we don't have it, so we can tell whether we need to
		 * bail out of pushing.
		 */
		if((o = readobject(theirs[ntheirs])) != nil){
			if((idx = findkey(map, nmap, sp[1])) != -1)
				map[idx].theirs = theirs[ntheirs];
			ntheirs++;
			unref(o);
		}


@@ 180,7 186,7 @@ sendpack(Conn *c)
			p = ancestor(a, b);
		if(!force && !hasheq(&m->theirs, &Zhash) && (a == nil || p != a)){
			fprint(2, "remote has diverged\n");
			werrstr("force needed");
			werrstr("remote diverged");
			flushpkt(c);
			return -1;
		}

M serve.c => serve.c +12 -11
@@ 362,7 362,7 @@ lockrepo(void)
int
updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
{
	char refpath[512];
	char refpath[512], buf[128];
	int i, newidx, hadref, fd, ret, lockfd;
	vlong newtm;
	Object *o;


@@ 378,19 378,19 @@ updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
	 */
	newtm = -23811206400;	
	if((lockfd = lockrepo()) == -1){
		werrstr("repo locked\n");
		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])){
				werrstr("old ref changed: %s", ref[i]);
				snprint(buf, sizeof(buf), "old ref changed: %s", ref[i]);
				goto error;
			}
		}
		if(snprint(refpath, sizeof(refpath), ".git/%s", ref[i]) == sizeof(refpath)){
			werrstr("ref path too long: %s", ref[i]);
			snprint(buf, sizeof(buf), "ref path too long: %s", ref[i]);
			goto error;
		}
		if(hasheq(&upd[i], &Zhash)){


@@ 398,11 398,11 @@ updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
			continue;
		}
		if((o = readobject(upd[i])) == nil){
			werrstr("update to nonexistent hash %H", upd[i]);
			snprint(buf, sizeof(buf), "update to nonexistent hash %H", upd[i]);
			goto error;
		}
		if(o->type != GCommit){
			werrstr("not commit: %H", upd[i]);
			snprint(buf, sizeof(buf), "not commit: %H", upd[i]);
			goto error;
		}
		if(o->commit->mtime > newtm){


@@ 411,11 411,11 @@ updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
		}
		unref(o);
		if((fd = create(refpath, OWRITE|OTRUNC, 0644)) == -1){
			werrstr("open ref: %r");
			snprint(buf, sizeof(buf), "open ref: %r");
			goto error;
		}
		if(fprint(fd, "%H", upd[i]) == -1){
			werrstr("upate ref: %r");
			snprint(buf, sizeof(buf), "upate ref: %r");
			close(fd);
			goto error;
		}


@@ 436,19 436,20 @@ updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
	 */
	if(resolveref(&h, "HEAD") == -1 && hadref == 0 && newidx != -1){
		if((fd = create(".git/HEAD", OWRITE|OTRUNC, 0644)) == -1){
			werrstr("open HEAD: %r");
			snprint(buf, sizeof(buf), "open HEAD: %r");
			goto error;
		}
		if(fprint(fd, "ref: %s", ref[0]) == -1){
			werrstr("write HEAD ref: %r");
			snprint(buf, sizeof(buf), "write HEAD ref: %r");
			goto error;
		}
		close(fd);
	}
	ret = 0;
error:
	fmtpkt(c, "ERR %r");
	fmtpkt(c, "ERR %s", buf);
	close(lockfd);
	werrstr(buf);
	return ret;
}


M util.c => util.c +5 -6
@@ 338,7 338,7 @@ qclear(Objq *q)
}

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


@@ 349,10 349,9 @@ qput(Objq *q, Object *o, int color, int dist)
	}
	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;
	q->heap[q->nheap].ctime = o->commit->ctime;
	for(i = q->nheap; i > 0; i = (i-1)/2){
		if(q->heap[i].mtime < q->heap[(i-1)/2].mtime)
		if(q->heap[i].ctime < q->heap[(i-1)/2].ctime)
			break;
		t = q->heap[i];
		q->heap[i] = q->heap[(i-1)/2];


@@ 379,9 378,9 @@ qpop(Objq *q, Qelt *e)
		m = i;
		l = 2*i+1;
		r = 2*i+2;
		if(l < q->nheap && q->heap[m].mtime < q->heap[l].mtime)
		if(l < q->nheap && q->heap[m].ctime < q->heap[l].ctime)
			m = l;
		if(r < q->nheap && q->heap[m].mtime < q->heap[r].mtime)
		if(r < q->nheap && q->heap[m].ctime < q->heap[r].ctime)
			m = r;
		if(m == i)
			break;

M walk.c => walk.c +3 -15
@@ 30,7 30,6 @@ enum {
Cache seencache[NCACHE];
int quiet;
int printflg;
char *base;
char *rstr = "R ";
char *tstr = "T ";
char *mstr = "M ";


@@ 210,21 209,19 @@ mismatch:
void
usage(void)
{
	fprint(2, "usage: %s [-qbc] [-f filt] [-b base] [paths...]\n", argv0);
	fprint(2, "usage: %s [-qbc] [-f filt] [paths...]\n", argv0);
	exits("usage");
}

void
main(int argc, char **argv)
{
	char *rpath, *tpath, *bpath, *cpath, buf[8], repo[512];
	char *rpath, *tpath, *bpath, buf[8], repo[512];
	char *p, *e;
	int i, dirty;
	Hash h;
	Wres r;
	Dir *d;

;
	ARGBEGIN{
	case 'q':
		quiet++;


@@ 245,9 242,6 @@ main(int argc, char **argv)
			default:	usage();		break;
		}
		break;
	case 'b':
		base = EARGF(usage());
		break;
	default:
		usage();
	}ARGEND


@@ 258,12 252,6 @@ main(int argc, char **argv)
		sysfatal("chdir: %r");
	if(access(".git/fs/ctl", AEXIST) != 0)
		sysfatal("no running git/fs");
	cpath = HDIR;
	if(base != nil){
		if(resolveref(&h, base) == -1)
			sysfatal("invalid base commit %s", base);
		cpath = smprint(".git/fs/object/%H/tree", h);
	}
	dirty = 0;
	memset(&r, 0, sizeof(r));
	if(printflg == 0)


@@ 301,7 289,7 @@ nextarg:
			goto next;
		rpath = smprint(RDIR"/%s", p);
		tpath = smprint(TDIR"/%s", p);
		bpath = smprint("%s/%s", cpath, p);
		bpath = smprint(HDIR"/%s", p);
		/* Fast path: we don't want to force access to the rpath. */
		if(d && sameqid(d, tpath)) {
			if(!quiet && (printflg & Tflg))