485 lines · 10.3 KB · C
RawDownload
#include <u.h>#include <libc.h>#include "git.h" typedef struct Objbuf Objbuf;struct Objbuf {	int off;	char *hdr;	int nhdr;	char *dat;	int ndat;}; enum {	Maxparents = 16,}; char	*authorname;char	*authoremail;char	*committername;char	*committeremail;char	*commitmsg;Hash	parents[Maxparents];int	nparents;Idxent	*idx;int	idxsz;int	nidx; intgitmode(Dirent *e){	if(e->islink)		return 0120000;	else if(e->ismod)		return 0160000;	else if(e->mode & DMDIR)		return 0040000;	else if(e->mode & 0100)		return 0100755;	else		return 0100644;} intnamecmp(void *pa, void *pb){	return strcmp(*(char**)pa, *(char**)pb);} intidxcmp(void *pa, void *pb){	Idxent *a, *b;	int c; 	a = (Idxent*)pa;	b = (Idxent*)pb;	if((c = strcmp(a->path, b->path)) != 0)		return c;	assert(a->order != b->order);	return a-> order < b->order ? -1 : 1;} static intbwrite(void *p, void *buf, int nbuf){	return Bwrite(p, buf, nbuf);} static intobjbytes(void *p, void *buf, int nbuf){	Objbuf *b;	int r, n, o;	char *s; 	b = p;	n = 0;	if(b->off < b->nhdr){		r = b->nhdr - b->off;		r = (nbuf < r) ? nbuf : r;		memcpy(buf, b->hdr, r);		b->off += r;		nbuf -= r;		n += r;	}	if(b->off < b->ndat + b->nhdr){		s = buf;		o = b->off - b->nhdr;		r = b->ndat - o;		r = (nbuf < r) ? nbuf : r;		memcpy(s + n, b->dat + o, r);		b->off += r;		n += r;	}	return n;} voidwriteobj(Hash *h, char *hdr, int nhdr, char *dat, int ndat){	Objbuf b = {.off=0, .hdr=hdr, .nhdr=nhdr, .dat=dat, .ndat=ndat};	char s[64], o[256];	SHA1state *st;	Biobuf *f;	int fd; 	st = sha1((uchar*)hdr, nhdr, nil, nil);	st = sha1((uchar*)dat, ndat, nil, st);	sha1(nil, 0, h->h, st); 	snprint(s, sizeof(s), "%H", *h);	fd = create(".git/objects", OREAD, DMDIR|0755);	close(fd);	snprint(o, sizeof(o), ".git/objects/%c%c", s[0], s[1]);	fd = create(o, OREAD, DMDIR | 0755);	close(fd);	snprint(o, sizeof(o), ".git/objects/%c%c/%s", s[0], s[1], s + 2);	if(readobject(*h) == nil){		if((f = Bopen(o, OWRITE)) == nil)			sysfatal("could not open %s: %r", o);		if(deflatezlib(f, bwrite, &b, objbytes, 9, 0) == -1)			sysfatal("could not write %s: %r", o);		Bterm(f);	}} intwritetree(Dirent *ent, int nent, Hash *h){	char *t, *txt, *etxt, hdr[128];	int nhdr, n;	Dirent *d, *p; 	t = emalloc((16+256+20) * nent);	txt = t;	etxt = t + (16+256+20) * nent; 	/* sqeeze out deleted entries */	n = 0;	p = ent;	for(d = ent; d != ent + nent; d++)		if(d->name)			p[n++] = *d;	nent = n; 	qsort(ent, nent, sizeof(Dirent), entcmp);	for(d = ent; d != ent + nent; d++){		if(strlen(d->name) >= 255)			sysfatal("overly long filename: %s", d->name);		t = seprint(t, etxt, "%o %s", gitmode(d), d->name) + 1;		memcpy(t, d->h.h, sizeof(d->h.h));		t += sizeof(d->h.h);	}	nhdr = snprint(hdr, sizeof(hdr), "%T %lld", GTree, (vlong)(t - txt)) + 1;	writeobj(h, hdr, nhdr, txt, t - txt);	free(txt);	return nent;} voidblobify(Dir *d, char *path, int *mode, Hash *bh){	char h[64], *buf;	int f, nh; 	if((d->mode & DMDIR) != 0)		sysfatal("not file: %s", path);	*mode = d->mode;	nh = snprint(h, sizeof(h), "%T %lld", GBlob, d->length) + 1;	if((f = open(path, OREAD)) == -1)		sysfatal("could not open %s: %r", path);	buf = emalloc(d->length);	if(readn(f, buf, d->length) != d->length)		sysfatal("could not read blob %s: %r", path);	writeobj(bh, h, nh, buf, d->length);	free(buf);	close(f);} inttracked(char *path){	int r, lo, hi, mid; 	lo = 0;	hi = nidx-1;	while(lo <= hi){		mid = (hi + lo) / 2;		r = strcmp(path, idx[mid].path);		if(r < 0)			hi = mid-1;		else if(r > 0)			lo = mid+1;		else			return idx[mid].state != 'R';	}	return 0; } Dirent*dirent(Dirent **ent, int *nent, char *name){	Dirent *d; 	assert(strchr(name, '/') == nil); 	for(d = *ent; d != *ent + *nent; d++)		if(d->name && strcmp(d->name, name) == 0)			return d;	*nent += 1;	*ent = erealloc(*ent, *nent * sizeof(Dirent));	d = *ent + (*nent - 1);	memset(d, 0, sizeof(*d));	d->name = estrdup(name);	return d;} inttreeify(Object *t, char **path, char **epath, int off, Hash *h){	int nent, ne, slash, isdir;	char *s, **p, **ep;	Dirent *e, *ent;	Object *o;	Dir *d; 	nent = t->tree->nent;	ent = eamalloc(nent, sizeof(*ent));	memcpy(ent, t->tree->ent, nent*sizeof(*ent));	for(p = path; p != epath; p = ep){		s = *p; 		/*		 * paths have been normalized already,		 * no leading or double-slashes allowed.		 */		assert(off <= strlen(s));		assert(off == 0 || s[off-1] == '/');		assert(s[off] != '\0' && s[off] != '/'); 		/* get next path element length (from off until '/' or nul) */		for(ne = 1; s[off+ne] != '\0' && s[off+ne] != '/'; ne++)			; 		/* truncate at '/' or nul */		slash = s[off + ne];		s[off + ne] = '\0'; 		/* skip over children (having s as prefix) */		for(ep = p + 1; slash && ep != epath; ep++){			if(strncmp(s, *ep, off + ne) != 0)				break;			if((*ep)[off+ne] != '\0' && (*ep)[off+ne] != '/')				break;		} 		d = dirstat(s);		e = dirent(&ent, &nent, s + off);		if(e->islink)			sysfatal("symlinks may not be modified: %s", s);		if(e->ismod)			sysfatal("submodules may not be modified: %s", s); 		s[off + ne] = slash; 		isdir = d != nil && (d->mode & DMDIR) != 0;		/*		 * exist? slash? dir?	track?		 * n      _      _      _      -> remove: file gone		 * y      n      n      y      -> blob: tracked non-dir		 * y      n      y      n      -> remove: file untracked		 * y      n      y      n      -> remove: file -> dir		 * y      n      y      y      -> remove: file -> dir		 * y      n      y      n      -> untracked dir, cli junk		 * y      y      y      n      -> recurse		 * y      y      y      y      -> recurse		 */		if(d == nil || !slash && isdir && tracked(s)){			/*			 * if a tracked file is removed or turned			 * into a dir, we want to delete it. We			 * only want to change files passed in, and			 * not ones along the way, so ignore files			 * that have a '/'.			 */			e->name = nil;			s[off + ne] = slash;			continue;		} else if(slash && isdir){			/*			 * If we have a list of entries that go into			 * a directory, create a tree node for this			 * entry, and recurse down.			 */			e->mode = DMDIR | 0755;			o = readobject(e->h);			if(o == nil || o->type != GTree)				o = emptydir();			/*			 * if after processing deletions, a tree is empty,			 * mark it for removal from the parent.			 *			 * Note, it is still written to the object store,			 * but this is fine -- and ensures that an empty			 * repository will continue to work.			 */			if(treeify(o, p, ep, off + ne + 1, &e->h) == 0)				e->name = nil;		}else if(!slash && !isdir){			/*			 * If the file was explicitly passed in and is			 * not a dir, we want to either remove it or			 * track it, depending on the state of the index.			 */			if(tracked(s) && !isdir)				blobify(d, s, &e->mode, &e->h);			else				e->name = nil;		}		free(d);	}	if(nent == 0)		sysfatal("%.*s: refusing to update empty directory", off, *path);	nent = writetree(ent, nent, h);	free(ent);	return nent;		}  voidmkcommit(Hash *c, vlong date, Hash tree){	char *s, h[64];	int ns, nh, i;	Fmt f; 	fmtstrinit(&f);	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", authorname, authoremail, date);	fmtprint(&f, "committer %s <%s> %lld +0000\n", committername, committeremail, date);	fmtprint(&f, "\n");	fmtprint(&f, "%s", commitmsg);	s = fmtstrflush(&f); 	ns = strlen(s);	nh = snprint(h, sizeof(h), "%T %d", GCommit, ns) + 1;	writeobj(c, h, nh, s, ns);	free(s);} Object*findroot(void){	Object *t, *c;	Hash h; 	if(resolveref(&h, "HEAD") == -1)		return emptydir();	if((c = readobject(h)) == nil || c->type != GCommit)		sysfatal("could not read HEAD %H", h);	if((t = readobject(c->commit->tree)) == nil)		sysfatal("could not read tree for commit %H", h);	return t;} voidusage(void){	fprint(2, "usage: %s -n name -e email -m message -d date [files...]\n", argv0);	exits("usage");} voidmain(int argc, char **argv){	char *ln, *dstr, *parts[4], cwd[1024];	int i, r, line, ncwd;	Hash th, ch;	vlong date;	Biobuf *f;	Object *t; 	gitinit(nil, 0, nil);	if(access(".git", AEXIST) != 0)		sysfatal("could not find git repo: %r");	if(getwd(cwd, sizeof(cwd)) == nil)		sysfatal("getcwd: %r");	dstr = nil;	date = time(nil);	ncwd = strlen(cwd); 	ARGBEGIN{	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");		if(resolveref(&parents[nparents++], EARGF(usage())) == -1)			sysfatal("invalid parent: %r");		break;	default:		usage();		break;	}ARGEND; 	if(commitmsg == nil)		sysfatal("missing message");	if(authorname == nil)		sysfatal("missing name");	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);	}	for(i = 0; i < argc; i++){		cleanname(argv[i]);		if(*argv[i] == '/' && strncmp(argv[i], cwd, ncwd) == 0)			argv[i] += ncwd;		while(*argv[i] == '/')			argv[i]++;	}	qsort(argv, argc, sizeof(*argv), namecmp); 	t = findroot();	nidx = 0;	idxsz = 32;	idx = emalloc(idxsz*sizeof(Idxent));	if((f = Bopen(".git/INDEX9", OREAD)) == nil)		sysfatal("open index: %r");	line = 0;	while((ln = Brdstr(f, '\n', 1)) != nil){		line++;		if(ln[0] == 0 || ln[0] == '\n')			continue;		if(getfields(ln, parts, nelem(parts), 0, " \t") != nelem(parts))			sysfatal(".git/INDEX9:%d: corrupt index", line);		if(nidx == idxsz){			idxsz += idxsz/2;			idx = realloc(idx, idxsz*sizeof(Idxent));		}		cleanname(parts[3]);		idx[nidx].state = *parts[0];		idx[nidx].qid = parseqid(parts[1]);		idx[nidx].mode = strtol(parts[2], nil, 8);		idx[nidx].path = strdup(parts[3]);		idx[nidx].order = nidx;		nidx++;		free(ln);	}	Bterm(f);	qsort(idx, nidx, sizeof(Idxent), idxcmp);	r = treeify(t, argv, argv + argc, 0, &th);	if(r == -1)		sysfatal("could not commit: %r");	mkcommit(&ch, date, th);	print("%H\n", ch);	exits(nil);}