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