Git: git9

git9 @ ca837be27a257b8dff9df887fe751f46136c17d3

#include <u.h>
#include <libc.h>
#include "git.h"

typedef struct Pfilt Pfilt;
struct Pfilt {
	char	*elt;
	int	show;
	Pfilt	*sub;
	int	nsub;
};

Biobuf	*out;
char	*queryexpr;
char	*commitid;
int	shortlog;

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

void
filteradd(Pfilt *pf, char *path)
{
	char *p, *e;
	int i;

	if((e = strchr(path, '/')) != nil)
		p = smprint("%.*s", (int)(e - path), path);
	else
		p = strdup(path);

	while(e != nil && *e == '/')
		e++;
	for(i = 0; i < pf->nsub; i++){
		if(strcmp(pf->sub[i].elt, p) == 0){
			pf->sub[i].show = pf->sub[i].show || (e == nil);
			if(e != nil)
				filteradd(&pf->sub[i], e);
			free(p);
			return;
		}
	}
	pf->sub = earealloc(pf->sub, pf->nsub+1, sizeof(Pfilt));
	pf->sub[pf->nsub].elt = p;
	pf->sub[pf->nsub].show = (e == nil);
	pf->sub[pf->nsub].nsub = 0;
	pf->sub[pf->nsub].sub = nil;
	if(e != nil)
		filteradd(&pf->sub[pf->nsub], e);
	pf->nsub++;
}

Hash
lookup(Pfilt *pf, Object *o)
{
	int i;

	for(i = 0; i < o->tree->nent; i++)
		if(strcmp(o->tree->ent[i].name, pf->elt) == 0)
			return o->tree->ent[i].h;
	return Zhash;
}

int
filtermatch1(Pfilt *pf, Object *t, Object *pt)
{
	Object *a, *b;
	Hash ha, hb;
	int i, r;

	if(pf->show)
		return 1;
	if(t->type != pt->type)
		return 1;
	if(t->type != GTree)
		return 0;

	for(i = 0; i < pf->nsub; i++){
		ha = lookup(&pf->sub[i], t);
		hb = lookup(&pf->sub[i], pt);
		if(hasheq(&ha, &hb))
			continue;
		if(hasheq(&ha, &Zhash) || hasheq(&hb, &Zhash))
			return 1;
		if((a = readobject(ha)) == nil)
			sysfatal("read %H: %r", ha);
		if((b = readobject(hb)) == nil)
			sysfatal("read %H: %r", hb);
		r = filtermatch1(&pf->sub[i], a, b);
		unref(a);
		unref(b);
		if(r)
			return 1;
	}
	return 0;
}

int
filtermatch(Object *o)
{
	Object *t, *p, *pt;
	int i, r;

	if(pathfilt == nil)
		return 1;
	if((t = readobject(o->commit->tree)) == nil)
		sysfatal("read %H: %r", o->commit->tree);
	for(i = 0; i < o->commit->nparent; i++){
		if((p = readobject(o->commit->parent[i])) == nil)
			sysfatal("read %H: %r", o->commit->parent[i]);
		if((pt = readobject(p->commit->tree)) == nil)
			sysfatal("read %H: %r", o->commit->tree);
		r = filtermatch1(pathfilt, t, pt);
		unref(p);
		unref(pt);
		if(r)
			return 1;
	}
	return 0;
}


static char*
nextline(char *p, char *e)
{
	for(; p != e; p++)
		if(*p == '\n')
			break;
	return p;
}

static void
show(Object *o)
{
	Tm tm;
	char *p, *q, *e;

	assert(o->type == GCommit);
	if(!filtermatch(o))
		return;

	if(shortlog){
		p = o->commit->msg;
		e = p + o->commit->nmsg;
		q = nextline(p, e);
		Bprint(out, "%H ", o->hash);
		Bwrite(out, p, q - p);
		Bputc(out, '\n');
	}else{
		tmtime(&tm, o->commit->mtime, tzload("local"));
		Bprint(out, "Hash:\t%H\n", o->hash);
		Bprint(out, "Author:\t%s\n", o->commit->author);
		Bprint(out, "Date:\t%τ\n", tmfmt(&tm, "WW MMM D hh:mm:ss z YYYY"));
		Bprint(out, "\n");
		p = o->commit->msg;
		e = p + o->commit->nmsg;
		for(; p != e; p = q){
			q = nextline(p, e);
			Bputc(out, '\t');
			Bwrite(out, p, q - p);
			Bputc(out, '\n');
			if(q != e)
				q++;
		}
		Bprint(out, "\n");
	}
	Bflush(out);
}

static void
showquery(char *q)
{
	Object *o;
	Hash *h;
	int n, i;

	if((n = resolverefs(&h, q)) == -1)
		sysfatal("resolve: %r");
	for(i = 0; i < n; i++){
		if((o = readobject(h[i])) == nil)
			sysfatal("read %H: %r", h[i]);
		show(o);
		unref(o);
	}
	exits(nil);
}

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;
	int i;
	Hash h;

	if(c == nil)
		c = "HEAD";
	if(resolveref(&h, c) == -1)
		sysfatal("resolve %s: %r", c);
	if((o = readobject(h)) == nil)
		sysfatal("load %H: %r", h);
	heapsz = 8;
	heap = eamalloc(heapsz, sizeof(Object*));
	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)
				sysfatal("load %H: %r", o->commit->parent[i]);
			qput(p);
		}
		unref(o);
	}
}

static void
usage(void)
{
	fprint(2, "usage: %s [-s] [-e expr | -c commit] files..\n", argv0);
	exits("usage");
}
	
void
main(int argc, char **argv)
{
	char path[1024], repo[1024], *p, *r;
	int i;

	ARGBEGIN{
	case 'e':
		queryexpr = EARGF(usage());
		break;
	case 'c':
		commitid = EARGF(usage());
		break;
	case 's':
		shortlog++;
		break;
	default:
		usage();
		break;
	}ARGEND;

	if(findrepo(repo, sizeof(repo)) == -1)
		sysfatal("find root: %r");
	if(argc != 0){
		if(getwd(path, sizeof(path)) == nil)
			sysfatal("getwd: %r");
		if(strlen(path) < strlen(repo))
			sysfatal("path changed");
		p = path + strlen(repo);
		pathfilt = emalloc(sizeof(Pfilt));
		for(i = 0; i < argc; i++){
			r = smprint("./%s/%s", p, argv[i]);
			cleanname(r);
			filteradd(pathfilt, r);
			free(r);
		}
	}
	if(chdir(repo) == -1)
		sysfatal("chdir: %r");

	gitinit();
	tmfmtinstall();
	out = Bfdopen(1, OWRITE);
	if(queryexpr != nil)
		showquery(queryexpr);
	else
		showcommits(commitid);
	exits(nil);
}