shithub: git9

Download patch

ref: 3887355c90fb6a438def9c44025d38f6b90195bc
parent: 26f165c02bff58b15564e77309a5e147ecda388c
author: Ori Bernstein <ori@eigenstate.org>
date: Sun Nov 17 23:56:19 EST 2019

git/commit and git/save now require a file list.

This change allows making commits that don't include
a full list of files. 'git/add [-r]' actions will
only take effect on files that are listed.

--- a/branch
+++ b/branch
@@ -76,7 +76,8 @@
 		d=`{basename -d $m}
 		mkdir -p $d
 		mkdir -p .git/index9/tracked/$d
-		cp  $basedir/tree/$m $m
+		if(test -f $m)
+			cp  $basedir/tree/$m $m
 		walk -eq $m > .git/index9/tracked/$m
 	}
 }
--- a/commit
+++ b/commit
@@ -3,7 +3,7 @@
 . /sys/lib/git/common.rc
 
 usage='
-	git/commit
+	git/commit files...
 '
 
 fn whoami{
@@ -41,15 +41,15 @@
 }
 
 fn editmsg{
-	echo '' > $msgfile.tmp
-	echo '# Commit message goes here.' >> $msgfile.tmp
 	echo '# Author: '$name' <'$email'>' >> $msgfile.tmp
-	echo '#' $nl'# ' ^ `$nl{git/walk -fAMR} >> $msgfile.tmp
+	echo '#' $nl'# ' ^ `$nl{git/walk -fAMR $files} >> $msgfile.tmp
+	echo '#' >> $msgfile.tmp
+	echo '# Commit message:' >> $msgfile.tmp
 	giteditor=`{git/conf core.editor}
 	if(~ $#editor 0)
 		editor=$giteditor
 	if(~ $#editor 0)
-		editor=sam
+		editor=hold
 	$editor $msgfile.tmp
 	if(~ `{grep -v '^[ 	]*($|#.*$)' $msgfile.tmp | wc -l} 0)
 		die 'empty commit message'
@@ -69,21 +69,25 @@
 	msg=`"{cat $msgfile}
 	if(! ~ $#parents 0)
 		pflags='-p'^$parents
-	hash=`{git/save -n $"name -e $"email  -m $"msg $pflags || die $status}
+	hash=`{git/save -n $"name -e $"email  -m $"msg $pflags $files || die $status}
 }
 
 fn update{
 	mkdir -p `{basename -d $refpath}
+	# Paranoia: let's not mangle the repo.
+	if(~ $#hash 0)
+		die 'botched commit'
 	echo $hash > $refpath
-	for(f in `$nl{git/walk -cfAM}){
-		mkdir -p `{basename -d $f}
-		walk -eq $f > .git/index9/tracked/$f
+	for(f in $files){
+		if(test -e .git/index9/removed/$f){
+			rm -f .git/index9/removed/$f
+			rm -f .git/index9/tracked/$f
+		}
+		if not{
+			mkdir -p `{basename -d $f}
+			walk -eq $f > .git/index9/tracked/$f
+		}
 	}
-	for(f in `$nl{git/walk -cfR}){
-		rm -f .git/index9/tracked/$f
-		rm -f .git/index9/removed/$f
-	}
-
 }
 
 fn cleanup{
@@ -95,7 +99,10 @@
 
 msgfile=/tmp/git-msg.$pid
 mkdir -p .git/refs
-if(git/walk -q)
+if(~ $#* 0)
+	usage
+files=`{git/walk -c $*}
+if(~ $status '' || ~ $#files 0asdf)
 	die 'nothing to commit'
 @{
 	flag e +
--- /dev/null
+++ b/common.rc
@@ -1,0 +1,20 @@
+nl='
+'
+
+fn die{
+	>[1=2] echo $0: $*
+	exit $"*
+}
+
+fn usage{
+	>[1=2] echo -n 'usage:' $usage
+	exit 'usage'
+}
+
+fn gitup{
+	gitroot=`{git/conf -r >[2]/dev/null}
+	if(~ $#gitroot 0)
+		die 'not a git repository'
+	cd $gitroot
+	git/fs
+}
--- a/fs.c
+++ b/fs.c
@@ -429,7 +429,7 @@
 			if(!w && o->tree->ent[i].modref)
 				w = modrefobj(&o->tree->ent[i]);
 			if(!w)
-				die("could not read object for %s", name);
+				die("could not read object for %s: %r", name);
 			q->type = (w->type == GTree) ? QTDIR : 0;
 			q->path = qpath(c, i, w->id, qdir);
 			c->mode = o->tree->ent[i].mode;
@@ -455,8 +455,10 @@
 			q->type = QTDIR;
 			q->path = qpath(p, 4, o->id, Qcommittree);
 			unref(c->obj);
-			c->obj = readobject(o->commit->tree);
 			c->mode = DMDIR | 0755;
+			c->obj = readobject(o->commit->tree);
+			if(c->obj == nil)
+				sysfatal("could not read object %H: %r", o->commit->tree);
 		}
 		else
 			e = Eexist;
--- a/git.1
+++ b/git.1
@@ -28,6 +28,7 @@
 ]
 .PP
 .B git/commit
+.I file...
 .PP
 .B git/conf
 [
--- a/import
+++ b/import
@@ -77,7 +77,7 @@
 				git/add -r $f
 		}
 		git/walk -fRMA
-		hash=`{git/save -n $name -e $email -m $msg -d $date $parents}
+		hash=`{git/save -n $name -e $email -m $msg -d $date $parents $files}
 		echo $hash > $refpath
 	'
 }
--- a/pack.c
+++ b/pack.c
@@ -766,7 +766,6 @@
 		m = strtol(buf, nil, 8);
 		/* FIXME: symlinks and other BS */
 		if(m == 0160000){
-			print("setting mode to link...\n");
 			t->mode |= DMDIR;
 			t->modref = 1;
 		}
--- a/query.c
+++ b/query.c
@@ -49,10 +49,8 @@
 				if(ap->mode == bp->mode && hasheq(&ap->h, &bp->h))
 					goto next;
 
-				if(ap->mode != bp->mode){
-					print("mode: %o -> %o\n", ap->mode, bp->mode);
+				if(ap->mode != bp->mode)
 					print("! %P%s\n", ap->name);
-				}
 				else if(!(ap->mode & DMDIR) || !(bp->mode & DMDIR))
 					print("@ %P%s\n", ap->name);
 				if((ap->mode & DMDIR) && (bp->mode & DMDIR)){
--- a/revert
+++ b/revert
@@ -21,6 +21,8 @@
 	}
 	shift
 }
+if(~ $#* 0)
+	usage
 
 rel=`{echo $rel | sed 's@^'$gitroot'/?@@'}
 if(~ $#rel 0)
--- a/save.c
+++ b/save.c
@@ -14,6 +14,55 @@
 	Maxparents = 16,
 };
 
+Object*
+emptydir(void)
+{
+	Object *t;
+
+	t = emalloc(sizeof(Object));
+	t->tree = emalloc(sizeof(Tinfo));
+	return t;
+}
+
+int
+gitmode(int m)
+{
+	int b;
+
+	if((m & 0111) || (m & DMDIR))
+		b = 0755;
+	else
+		b = 0644;
+	return b | ((m & DMDIR) ? 0040000 : 0100000);
+}
+
+int
+entcmp(void *pa, void *pb)
+{
+	char abuf[256], bbuf[256], *ae, *be;
+	Dirent *a, *b;
+
+	a = pa;
+	b = pb;
+	/*
+	 * If the files have the same name, they're equal.
+	 * Otherwise, If they're trees, they sort as thoug
+	 * there was a trailing slash.
+	 *
+	 * Wat.
+	 */
+	if(strcmp(a->name, b->name) == 0)
+		return 0;
+
+	ae = seprint(abuf, abuf + sizeof(abuf) - 1, a->name);
+	be = seprint(bbuf, bbuf + sizeof(bbuf) - 1, b->name);
+	if(a->mode & DMDIR)
+		*ae = '/';
+	if(b->mode & DMDIR)
+		*be = '/';
+	return strcmp(abuf, bbuf);
+}
+
 static int
 bwrite(void *p, void *buf, int nbuf)
 {
@@ -61,6 +110,7 @@
 	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);
@@ -77,31 +127,64 @@
 	}
 }
 
-int
-gitmode(int m)
+void
+writetree(Dirent *ent, int nent, Hash *h)
 {
-	return (m & 0777) | ((m & DMDIR) ? 0040000 : 0100000);
+	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->mode), d->name) + 1;
+		memcpy(t, d->h.h, sizeof(d->h.h));
+		t += sizeof(d->h.h);
+	}
+	nhdr = snprint(hdr, sizeof(hdr), "%T %zd", GTree, t - txt) + 1;
+	writeobj(h, hdr, nhdr, txt, t - txt);
+	free(txt);
 }
 
 void
-blobify(char *path, vlong size, Hash *bh)
+blobify(char *path, int *mode, Hash *bh)
 {
-	char h[64], *d;
+	char h[64], *buf;
 	int f, nh;
+	Dir *d;
 
-	nh = snprint(h, sizeof(h), "%T %lld", GBlob, size) + 1;
+	if((d = dirstat(path)) == nil)
+		sysfatal("could not stat %s: %r", path);
+	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);
-	d = emalloc(size);
-	if(readn(f, d, size) != size)
+	buf = emalloc(d->length);
+	if(readn(f, buf, d->length) != d->length)
 		sysfatal("could not read blob %s: %r", path);
-	writeobj(bh, h, nh, d, size);
-	close(f);
+	writeobj(bh, h, nh, buf, d->length);
+	free(buf);
 	free(d);
+	close(f);
 }
 
 int
-tracked(char *path)
+tracked(char *path, int *explicit)
 {
 	Dir *d;
 	char ipath[256];
@@ -123,78 +206,91 @@
 	if(access(ipath, AEXIST) == 0)
 		return 1;
 
+	/* unknown */
+	*explicit = 0;
 	return 0;
 }
 
 int
-dircmp(void *pa, void *pb)
+pathelt(char *buf, int nbuf, char *p, int *isdir)
 {
-	char aname[256], bname[256], c;
-	Dir *a, *b;
+	char *b;
 
-	a = pa;
-	b = pb;
-	/*
-	 * If the files have the same name, they're equal.
-	 * Otherwise, If they're trees, they sort as thoug
-	 * there was a trailing slash.
-	 *
-	 * Wat.
-	 */
-	if(strcmp(a->name, b->name) == 0){
-		snprint(aname, sizeof(aname), "%s", a->name);
-		snprint(bname, sizeof(bname), "%s", b->name);
-	}else{
-		c = (a->qid.type & QTDIR) ? '/' : 0;
-		snprint(aname, sizeof(aname), "%s%c", a->name, c);
-		c = (b->qid.type & QTDIR) ? '/' : 0;
-		snprint(bname, sizeof(bname), "%s%c", b->name, c);
-	}
+	b = buf;
+	if(*p == '/')
+		p++;
+	while(*p && *p != '/' && b != buf + nbuf)
+		*b++ = *p++;
+	*b = '\0';
+	*isdir = (*p == '/');
+	return b - buf;
+}
 
-	return strcmp(aname, bname);
+Dirent*
+dirent(Dirent **ent, int *nent, char *name)
+{
+	Dirent *d;
+
+	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);
+	d->name = estrdup(name);
+	return d;
 }
 
 int
-treeify(char *path, Hash *th)
+treeify(Object *t, char **path, char **epath, int off, Hash *h)
 {
-	char *t, h[64], l[256], ep[256];
-	int nd, nl, nt, nh, i, s;
-	Hash eh;
-	Dir *d;
-		
-	if((nd = slurpdir(path, &d)) == -1)
-		sysfatal("could not read %s", path);
-	if(nd == 0)
-		return 0;
+	int r, ne, nsub, nent, isdir, untrack;
+	char **p, **ep;
+	char elt[256];
+	Object **sub;
+	Dirent *e, *ent;
 
-	t = nil;
-	nt = 0;
-	qsort(d, nd, sizeof(Dir), dircmp);
-	for(i = 0; i < nd; i++){
-		snprint(ep, sizeof(ep), "%s/%s", path, d[i].name);
-		if(strcmp(d[i].name, ".git") == 0)
-			continue;
-		if(!tracked(ep))
-			continue;
-		if((d[i].qid.type & QTDIR) == 0)
-			blobify(ep, d[i].length, &eh);
-		else if(treeify(ep, &eh) == 0)
-			continue;
-
-		nl = snprint(l, sizeof(l), "%o %s", gitmode(d[i].mode), d[i].name);
-		s = nt + nl + sizeof(eh.h) + 1;
-		t = realloc(t, s);
-		memcpy(t + nt, l, nl + 1);
-		memcpy(t + nt + nl + 1, eh.h, sizeof(eh.h));
-		nt = s;
+	r = -1;
+	nsub = 0;
+	nent = t->tree->nent;
+	ent = emalloc(nent * sizeof(*ent));
+	sub = emalloc((epath - path)*sizeof(Object*));
+	memcpy(ent, t->tree->ent, nent*sizeof(*ent));
+	for(p = path; p != epath; p = ep){
+		ne = pathelt(elt, sizeof(elt), *p + off, &isdir);
+		for(ep = p; ep != epath; ep++){
+			if(strncmp(elt, *ep + off, ne) != 0)
+				break;
+			if((*ep)[off+ne] != '\0' && (*ep)[off+ne] != '/')
+				break;
+		}
+		e = dirent(&ent, &nent, elt);
+		if(isdir){
+			e->mode = DMDIR | 0755;
+			sub[nsub] = readobject(e->h);
+			if(sub[nsub] == nil || sub[nsub]->type != GTree)
+				sub[nsub] = emptydir();
+			if(treeify(sub[nsub], p, ep, off + ne + 1, &e->h) == -1)
+				goto err;
+		}else{
+			if(tracked(*p, &untrack))
+				blobify(*p, &e->mode, &e->h);
+			else if(untrack)
+				e->name = nil;
+			else
+				sysfatal("untracked file %s", *p);
+		}
 	}
-	free(d);
-	nh = snprint(h, sizeof(h), "%T %d", GTree, nt) + 1;
-	if(nh >= sizeof(h))
-		sysfatal("overlong header");
-	writeobj(th, h, nh, t, nt);
-	free(t);
-	return nd;
+	if(nent == 0){
+		werrstr("%.*s: empty directory", off, *path);
+		goto err;
+	}
+
+	writetree(ent, nent, h);
+	r = 0;
+err:
+	free(sub);
+	return r;		
 }
 
 
@@ -221,10 +317,27 @@
 	free(s);
 }
 
+Object*
+findroot(void)
+{
+	Object *t, *c;
+	Hash h;
+
+	if(resolveref(&h, "HEAD") == -1){
+		fprint(2, "empty HEAD ref\n");
+		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;
+}
+
 void
 usage(void)
 {
-	fprint(2, "usage: git/commit -n name -e email -m message -d dir");
+	fprint(2, "usage: %s -n name -e email -m message -d date files...\n", argv0);
 	exits("usage");
 }
 
@@ -231,10 +344,11 @@
 void
 main(int argc, char **argv)
 {
-	Hash c, t, parents[Maxparents];
+	Hash th, ch, parents[Maxparents];
 	char *msg, *name, *email, *dstr;
-	int r, nparents;
+	int i, r, nparents;
 	vlong date;
+	Object *t;
 
 	msg = nil;
 	name = nil;
@@ -258,27 +372,30 @@
 		usage();
 	}ARGEND;
 
-	if(!msg) sysfatal("missing message");
-	if(!name) sysfatal("missing name");
-	if(!email) sysfatal("missing email");
+	if(!msg)
+		sysfatal("missing message");
+	if(!name)
+		sysfatal("missing name");
+	if(!email)
+		sysfatal("missing email");
 	if(dstr){
 		date=strtoll(dstr, &dstr, 10);
 		if(strlen(dstr) != 0)
 			sysfatal("could not parse date %s", dstr);
 	}
-		
-	if(!msg || !name)
+	if(argc == 0 || msg == nil || name == nil)
 		usage();
+	for(i = 0; i < argc; i++)
+		cleanname(argv[i]);
 
 	gitinit();
 	if(access(".git", AEXIST) != 0)
 		sysfatal("could not find git repo: %r");
-	r = treeify(".", &t);
+	t = findroot();
+	r = treeify(t, argv, argv + argc, 0, &th);
 	if(r == -1)
 		sysfatal("could not commit: %r\n");
-	if(r == 0)
-		sysfatal("empty commit: aborting");
-	mkcommit(&c, msg, name, email, date, parents, nparents, t);
-	print("%H\n", c);
+	mkcommit(&ch, msg, name, email, date, parents, nparents, th);
+	print("%H\n", ch);
 	exits(nil);
 }
--- a/walk.c
+++ b/walk.c
@@ -2,7 +2,7 @@
 #include <libc.h>
 #include "git.h"
 
-#define NCACHE 256
+#define NCACHE 4096
 #define TDIR ".git/index9/tracked"
 #define RDIR ".git/index9/removed"
 #define HDIR "/mnt/git/HEAD/tree"
@@ -62,6 +62,15 @@
 	return 0;
 }
 
+void
+grow(Wres *r)
+{
+	if(r->npath == r->pathsz){
+		r->pathsz = 2*r->pathsz + 1;
+		r->path = erealloc(r->path, r->pathsz * sizeof(char*));
+	}
+}
+
 int
 readpaths(Wres *r, char *pfx, char *dir)
 {
@@ -69,6 +78,7 @@
 	Dir *d;
 	int fd, ret, i, n;
 
+	d = nil;
 	ret = -1;
 	sep = "";
 	if(dir[0] != 0)
@@ -79,8 +89,10 @@
 		goto error;
 	while((n = dirread(fd, &d)) > 0){
 		for(i = 0; i < n; i++){
-			if(seen(&d[i]))
+			if(seen(&d[i])){
+				print("seen %s\n", full);
 				continue;
+			}
 			if(d[i].qid.type & QTDIR){
 				if((sub = smprint("%s%s%s", dir, sep, d[i].name)) == nil)
 					sysfatal("smprint: %r");
@@ -90,21 +102,18 @@
 				}
 				free(sub);
 			}else{
-				if(r->npath == r->pathsz){
-					r->pathsz = 2*r->pathsz + 1;
-					r->path = erealloc(r->path, r->pathsz * sizeof(char*));
-				}
+				grow(r);
 				if((f = smprint("%s%s%s", dir, sep, d[i].name)) == nil)
 					sysfatal("smprint: %r");
 				r->path[r->npath++] = f;
 			}
 		}
+		free(d);
 	}
 	ret = r->npath;
 error:
 	close(fd);
 	free(full);
-	free(d);
 	return ret;
 }
 
@@ -217,7 +226,7 @@
 void
 main(int argc, char **argv)
 {
-	char rmpath[256], tpath[256], bpath[256], buf[8];
+	char *rpath, *tpath, *bpath, buf[8];
 	char *p, *e;
 	int i, dirty;
 	Wres r;
@@ -249,9 +258,7 @@
 
 	findroot();
 	dirty = 0;
-	r.path = nil;
-	r.npath = 0;
-	r.pathsz = 0;
+	memset(&r, 0, sizeof(r));
 	if(access("/mnt/git/ctl", AEXIST) != 0)
 		sysfatal("git/fs does not seem to be running");
 	if(printflg == 0)
@@ -262,28 +269,35 @@
 		if(access(RDIR, AEXIST) == 0 && readpaths(&r, RDIR, "") == -1)
 			sysfatal("read removed: %r");
 	}else{
-		r.path = emalloc(argc*sizeof(char*));
-		r.pathsz = argc;
 		for(i = 0; i < argc; i++){
-			snprint(tpath, sizeof(tpath), TDIR"/%s", argv[i]);
-			if(access(tpath, AEXIST) == 0)
+			tpath = smprint(TDIR"/%s", argv[i]);
+			rpath = smprint(RDIR"/%s", argv[i]);
+			if((d = dirstat(tpath)) == nil && (d = dirstat(rpath)) == nil)
+				goto nextarg;
+			if(d->mode & DMDIR){
+				readpaths(&r, TDIR, argv[i]);
+				readpaths(&r, RDIR, argv[i]);
+			}else{
+				grow(&r);
 				r.path[r.npath++] = estrdup(argv[i]);
+			}
+nextarg:
+			free(tpath);
+			free(rpath);
+			free(d);
 		}
-	}		
+	}
 	dedup(&r);
 
 	for(i = 0; i < r.npath; i++){
 		p = r.path[i];
-		snprint(rmpath, sizeof(rmpath), RDIR"/%s", p);
-		snprint(tpath, sizeof(tpath), TDIR"/%s", p);
-		snprint(bpath, sizeof(bpath), HDIR"/%s", p);
 		d = dirstat(p);
 		if(d && d->mode & DMDIR)
 			goto next;
-		if(sameqid(d, tpath)){
-			if(!quiet && (printflg & Tflg))
-				print("%s%s\n", tstr, p);
-		}else if(access(p, AEXIST) != 0 || access(rmpath, AEXIST) == 0){
+		rpath = smprint(RDIR"/%s", p);
+		tpath = smprint(TDIR"/%s", p);
+		bpath = smprint(HDIR"/%s", p);
+		if(access(rpath, AEXIST) == 0){
 			dirty |= Mflg;
 			if(!quiet && (printflg & Rflg))
 				print("%s%s\n", rstr, p);
@@ -291,14 +305,17 @@
 			dirty |= Aflg;
 			if(!quiet && (printflg & Aflg))
 				print("%s%s\n", astr, p);
-		}else if(!samedata(p, bpath)){
+		}else if(sameqid(d, tpath) || samedata(p, bpath)){
+			if(!quiet && (printflg & Tflg))
+				print("%s%s\n", tstr, p);
+		}else{
 			dirty |= Mflg;
 			if(!quiet && (printflg & Mflg))
 				print("%s%s\n", mstr, p);
-		}else{
-			if(!quiet && (printflg & Tflg))
-				print("%s%s\n", tstr, p);
 		}
+		free(rpath);
+		free(tpath);
+		free(bpath);
 next:
 		free(d);
 	}