ref: 6bd7215355f1a2aa90c6166d3b2b6dae1f0711c2
dir: /sys/src/cmd/git/walk.c/
#include <u.h>
#include <libc.h>
#include "git.h"
typedef struct Seen Seen;
typedef struct Idxed Idxed;
typedef struct Idxent Idxent;
#define NCACHE 4096
enum {
Rflg = 1 << 0,
Mflg = 1 << 1,
Aflg = 1 << 2,
Uflg = 1 << 3,
/* everything after this is not an error */
Tflg = 1 << 4,
};
struct Seen {
Dir* cache;
int n;
int max;
};
struct Idxed {
char** cache;
int n;
int max;
};
Seen seentab[NCACHE];
Idxed idxtab[NCACHE];
char repopath[1024];
char wdirpath[1024];
char *rstr = "R ";
char *mstr = "M ";
char *astr = "A ";
char *ustr = "U ";
char *tstr = "T ";
char *bdir = ".git/fs/HEAD/tree";
int useidx = 1;
int nrel;
int quiet;
int dirty;
int printflg;
Idxent *idx;
int idxsz;
int nidx;
int staleidx;
Idxent *wdir;
int wdirsz;
int nwdir;
int loadwdir(char*);
int
seen(Dir *dir)
{
Seen *c;
Dir *dp;
int i;
c = &seentab[dir->qid.path&(NCACHE-1)];
dp = c->cache;
for(i=0; i<c->n; i++, dp++)
if(dir->qid.path == dp->qid.path
&& dir->qid.type == dp->qid.type
&& dir->dev == dp->dev)
return 1;
if(c->n == c->max){
if (c->max == 0)
c->max = 8;
else
c->max += c->max/2;
c->cache = realloc(c->cache, c->max*sizeof(Dir));
if(c->cache == nil)
sysfatal("realloc: %r");
}
c->cache[c->n++] = *dir;
return 0;
}
int
checkedin(Idxent *e, int change)
{
char *p;
int r;
p = smprint("%s/%s", bdir, e->path);
r = access(p, AEXIST);
if(r == 0 && change){
if(e->state != 'R')
e->state = 'T';
staleidx = 1;
}
free(p);
return r == 0;
}
int
indexed(char *path, int isdir)
{
int lo, hi, mid, n, r;
char *s;
if(!useidx){
s = smprint("%s/%s", bdir, path);
r = access(s, AEXIST);
free(s);
return r == 0;
}
s = path;
if(isdir)
s = smprint("%s/", path);
r = -1;
lo = 0;
hi = nidx-1;
n = strlen(s);
while(lo <= hi){
mid = (hi + lo) / 2;
if(isdir)
r = strncmp(s, idx[mid].path, n);
else
r = strcmp(s, idx[mid].path);
if(r < 0)
hi = mid-1;
else if(r > 0)
lo = mid+1;
else
break;
}
if(isdir)
free(s);
return r == 0;
}
int
idxcmp(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;
/* order is unique */
return a-> order < b->order ? -1 : 1;
}
/*
* compares whether the indexed entry 'a'
* has the same contents and mode as
* the entry on disk 'b'; if the indexed
* entry is nil, does a deep comparison
* of the checked out file and the file
* checked in.
*/
int
samedata(Idxent *a, Idxent *b)
{
char *gitpath, ba[IOUNIT], bb[IOUNIT];
int fa, fb, na, nb, same;
Dir *da, *db;
if(a != nil){
if(a->qid.path == b->qid.path
&& a->qid.vers == b->qid.vers
&& a->qid.type == b->qid.type
&& a->mode == b->mode
&& a->mode != 0)
return 1;
}
same = 0;
da = nil;
db = nil;
if((gitpath = smprint("%s/%s", bdir, b->path)) == nil)
sysfatal("smprint: %r");
fa = open(gitpath, OREAD);
fb = open(b->path, OREAD);
if(fa == -1 || fb == -1)
goto mismatch;
da = dirfstat(fa);
db = dirfstat(fb);
if(da == nil || db == nil)
goto mismatch;
if((da->mode&0100) != (db->mode&0100))
goto mismatch;
if(da->length != db->length)
goto mismatch;
while(1){
if((na = readn(fa, ba, sizeof(ba))) == -1)
goto mismatch;
if((nb = readn(fb, bb, sizeof(bb))) == -1)
goto mismatch;
if(na != nb)
goto mismatch;
if(na == 0)
break;
if(memcmp(ba, bb, na) != 0)
goto mismatch;
}
if(a != nil){
a->qid = db->qid;
a->mode = db->mode;
staleidx = 1;
}
same = 1;
mismatch:
free(da);
free(db);
if(fa != -1)
close(fa);
if(fb != -1)
close(fb);
return same;
}
int
loadent(char *dir, Dir *d, int fullpath)
{
char *path;
int ret, isdir;
Idxent *e;
if(fullpath)
path = strdup(dir);
else
path = smprint("%s/%s", dir, d->name);
if(path == nil)
sysfatal("smprint: %r");
ret = 0;
isdir = d->qid.type & QTDIR;
cleanname(path);
if((printflg & Uflg) == 0 && !indexed(path, isdir))
return 0;
if(isdir){
ret = loadwdir(path);
free(path);
}else{
if(nwdir == wdirsz){
wdirsz += wdirsz/2;
wdir = erealloc(wdir, wdirsz*sizeof(Idxent));
}
e = wdir + nwdir;
e->path = path;
e->qid = d->qid;
e->mode = d->mode;
e->order = nwdir;
e->state = 'T';
nwdir++;
}
return ret;
}
int
loadwdir(char *path)
{
int fd, ret, i, n;
Dir *d, *e;
d = nil;
e = nil;
ret = -1;
cleanname(path);
if(strncmp(path, ".git/", 5) == 0)
return 0;
if((fd = open(path, OREAD)) < 0)
goto error;
if((e = dirfstat(fd)) == nil)
sysfatal("fstat: %r");
if(e->qid.type & QTDIR)
while((n = dirread(fd, &d)) > 0){
for(i = 0; i < n; i++)
if(loadent(path, &d[i], 0) == -1)
goto error;
free(d);
}
else{
if(loadent(path, e, 1) == -1)
goto error;
}
ret = 0;
error:
free(e);
if(fd != -1)
close(fd);
return ret;
}
int
pfxmatch(char *p, char **pfx, int *pfxlen, int npfx)
{
int i;
if(npfx == 0)
return 1;
for(i = 0; i < npfx; i++){
if(strncmp(p, pfx[i], pfxlen[i]) != 0)
continue;
if(p[pfxlen[i]] == '/' || p[pfxlen[i]] == 0)
return 1;
if(strcmp(pfx[i], ".") == 0 || *pfx[i] == 0)
return 1;
}
return 0;
}
char*
reporel(char *s)
{
char *p;
int n;
if(*s == '/')
s = strdup(s);
else
s = smprint("%s/%s", wdirpath, s);
p = cleanname(s);
n = strlen(repopath);
if(strncmp(s, repopath, n) != 0)
sysfatal("path outside repo: %s", s);
p += n;
if(*p == '/')
p++;
memmove(s, p, strlen(p)+1);
return s;
}
void
show(Biobuf *o, int flg, char *str, char *path)
{
dirty |= flg;
if(!quiet && (printflg & flg))
Bprint(o, "%s%s\n", str, path);
}
void
usage(void)
{
fprint(2, "usage: %s [-qbc] [-f filt] [-b base] [paths...]\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
char *p, *e, *ln, *base, **argrel, *parts[4], xbuf[8];
int i, j, c, line, wfd, *argn;
Biobuf *f, *o, *w;
Hash h, hd;
Dir rn;
gitinit();
if(access(".git/fs/ctl", AEXIST) != 0)
sysfatal("no running git/fs");
if(getwd(wdirpath, sizeof(wdirpath)) == nil)
sysfatal("getwd: %r");
if(findrepo(repopath, sizeof(repopath), &nrel) == -1)
sysfatal("find root: %r");
if(chdir(repopath) == -1)
sysfatal("chdir: %r");
ARGBEGIN{
case 'q':
quiet++;
break;
case 'c':
rstr = "";
tstr = "";
mstr = "";
astr = "";
ustr = "";
break;
case 'f':
for(p = EARGF(usage()); *p; p++)
switch(*p){
case 'T': printflg |= Tflg; break;
case 'A': printflg |= Aflg; break;
case 'M': printflg |= Mflg; break;
case 'R': printflg |= Rflg; break;
case 'U': printflg |= Uflg; break;
default: usage(); break;
}
break;
case 'b':
useidx = 0;
base = EARGF(usage());
if(resolveref(&h, base) == -1)
sysfatal("no such ref '%s'", base);
/* optimization: we're a lot faster when using the index */
if(resolveref(&hd, "HEAD") == 0 && hasheq(&h, &hd))
useidx = 1;
bdir = smprint(".git/fs/object/%H/tree", h);
break;
default:
usage();
}ARGEND;
if(printflg == 0)
printflg = Tflg | Aflg | Mflg | Rflg;
nidx = 0;
idxsz = 32;
idx = emalloc(idxsz*sizeof(Idxent));
nwdir = 0;
wdirsz = 32;
wdir = emalloc(wdirsz*sizeof(Idxent));
argrel = emalloc(argc*sizeof(char*));
argn = emalloc(argc*sizeof(int));
for(i = 0; i < argc; i++){
argrel[i] = reporel(argv[i]);
argn[i] = strlen(argrel[i]);
}
if((o = Bfdopen(1, OWRITE)) == nil)
sysfatal("open out: %r");
if(useidx){
if((f = Bopen(".git/INDEX9", OREAD)) == nil){
fprint(2, "open index: %r\n");
if(access(".git/index9", AEXIST) == 0){
fprint(2, "index format conversion needed:\n");
fprint(2, "\tcd %s && git/fs\n", repopath);
fprint(2, "\t@{cd .git/index9/removed >[2]/dev/null && walk -f | sed 's/^/R NOQID 0 /'} >> .git/INDEX9\n");
fprint(2, "\t@{cd .git/fs/HEAD/tree && walk -f | sed 's/^/T NOQID 0 /'} >> .git/INDEX9\n");
}
exits("noindex");
}
line = 0;
while((ln = Brdstr(f, '\n', 1)) != nil){
line++;
/* allow blank lines */
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]);
if(strncmp(parts[3], ".git/", 5) == 0){
staleidx = 1;
free(ln);
continue;
}
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);
}
qsort(idx, nidx, sizeof(Idxent), idxcmp);
}
for(i = 0; i < argc; i++){
argrel[i] = reporel(argv[i]);
argn[i] = strlen(argrel[i]);
}
if(argc == 0)
loadwdir(".");
else for(i = 0; i < argc; i++)
loadwdir(argrel[i]);
qsort(wdir, nwdir, sizeof(Idxent), idxcmp);
for(i = 0; i < argc; i++){
argrel[i] = reporel(argv[i]);
argn[i] = strlen(argrel[i]);
}
i = 0;
j = 0;
while(i < nidx || j < nwdir){
/* find the last entry we tracked for a path */
while(i+1 < nidx && strcmp(idx[i].path, idx[i+1].path) == 0){
staleidx = 1;
i++;
}
while(j+1 < nwdir && strcmp(wdir[j].path, wdir[j+1].path) == 0)
j++;
if(i < nidx && !pfxmatch(idx[i].path, argrel, argn, argc)){
i++;
continue;
}
if(i >= nidx)
c = 1;
else if(j >= nwdir)
c = -1;
else
c = strcmp(idx[i].path, wdir[j].path);
/* exists in both index and on disk */
if(c == 0){
if(idx[i].state == 'R'){
if(checkedin(&idx[i], 0))
show(o, Rflg, rstr, idx[i].path);
else{
idx[i].state = 'U';
staleidx = 1;
}
}else if(idx[i].state == 'A' && !checkedin(&idx[i], 1))
show(o, Aflg, astr, idx[i].path);
else if(!samedata(&idx[i], &wdir[j]))
show(o, Mflg, mstr, idx[i].path);
else
show(o, Tflg, tstr, idx[i].path);
i++;
j++;
/* only exists in index */
}else if(c < 0){
if(checkedin(&idx[i], 0))
show(o, Rflg, rstr, idx[i].path);
i++;
/* only exists on disk */
}else{
if(!useidx && checkedin(&wdir[j], 0)){
if(samedata(nil, &wdir[j]))
show(o, Tflg, tstr, wdir[j].path);
else
show(o, Mflg, mstr, wdir[j].path);
}else if(printflg & Uflg && pfxmatch(idx[i].path, argrel, argn, argc))
show(o, Uflg, ustr, wdir[j].path);
j++;
}
}
Bterm(o);
if(useidx && staleidx)
if((wfd = create(".git/INDEX9.new", OWRITE, 0644)) != -1){
if((w = Bfdopen(wfd, OWRITE)) == nil){
close(wfd);
goto Nope;
}
for(i = 0; i < nidx; i++){
while(i+1 < nidx && strcmp(idx[i].path, idx[i+1].path) == 0)
i++;
if(idx[i].state == 'U')
continue;
Bprint(w, "%c %Q %o %s\n",
idx[i].state,
idx[i].qid,
idx[i].mode,
idx[i].path);
}
Bterm(w);
nulldir(&rn);
rn.name = "INDEX9";
if(remove(".git/INDEX9") == -1)
goto Nope;
if(dirwstat(".git/INDEX9.new", &rn) == -1)
sysfatal("rename: %r");
}
Nope:
if(!dirty)
exits(nil);
p = xbuf;
e = p + sizeof(xbuf);
for(i = 0; (1 << i) != Tflg; i++)
if(dirty & (1 << i))
p = seprint(p, e, "%c", "RMAUT"[i]);
exits(xbuf);
}