![Ori Bernstein](/assets/img/avatar_default.png)
A while ago, qwx noticed that we clobbered the exec bit when merging files. This is not what we want, so we changed the operator precedence to avoid merging dirty files implicitly. But we do want to merge, because it's convenient for maintaining permissions. So, instead, we should do a 3 way merge of the exec bit. This patch does that, as well as reverting the rollback of that change. While we're here, we adjust the timestamps correctly in git/branch. This requires changes to git/fs, because without an open handler, lib9p allows opening any file with any mode, which confuses 'test -x'.
907 lines
17 KiB
C
907 lines
17 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
#include <ctype.h>
|
|
#include <fcall.h>
|
|
#include <thread.h>
|
|
#include <9p.h>
|
|
|
|
#include "git.h"
|
|
|
|
enum {
|
|
Qroot,
|
|
Qhead,
|
|
Qbranch,
|
|
Qcommit,
|
|
Qmsg,
|
|
Qparent,
|
|
Qtree,
|
|
Qcdata,
|
|
Qhash,
|
|
Qauthor,
|
|
Qcommitter,
|
|
Qobject,
|
|
Qctl,
|
|
Qmax,
|
|
Internal=1<<7,
|
|
};
|
|
|
|
typedef struct Gitaux Gitaux;
|
|
typedef struct Crumb Crumb;
|
|
typedef struct Cache Cache;
|
|
typedef struct Uqid Uqid;
|
|
struct Crumb {
|
|
char *name;
|
|
Object *obj;
|
|
Qid qid;
|
|
int mode;
|
|
vlong mtime;
|
|
};
|
|
|
|
struct Gitaux {
|
|
int ncrumb;
|
|
Crumb *crumb;
|
|
char *refpath;
|
|
int qdir;
|
|
|
|
/* For listing object dir */
|
|
Objlist *ols;
|
|
Object *olslast;
|
|
};
|
|
|
|
struct Uqid {
|
|
vlong uqid;
|
|
|
|
vlong ppath;
|
|
vlong oid;
|
|
int t;
|
|
int idx;
|
|
};
|
|
|
|
struct Cache {
|
|
Uqid *cache;
|
|
int n;
|
|
int max;
|
|
};
|
|
|
|
char *qroot[] = {
|
|
"HEAD",
|
|
"branch",
|
|
"object",
|
|
"ctl",
|
|
};
|
|
|
|
#define Eperm "permission denied"
|
|
#define Eexist "does not exist"
|
|
#define E2long "path too long"
|
|
#define Enodir "not a directory"
|
|
#define Erepo "unable to read repo"
|
|
#define Eobject "invalid object"
|
|
#define Egreg "wat"
|
|
#define Ebadobj "invalid object"
|
|
|
|
char gitdir[512];
|
|
char *username;
|
|
char *groupname;
|
|
char *mntpt = ".git/fs";
|
|
char **branches = nil;
|
|
Cache uqidcache[512];
|
|
vlong nextqid = Qmax;
|
|
|
|
static Object* walklink(Gitaux *, char *, int, int, int*);
|
|
|
|
vlong
|
|
qpath(Crumb *p, int idx, vlong id, vlong t)
|
|
{
|
|
int h, i;
|
|
vlong pp;
|
|
Cache *c;
|
|
Uqid *u;
|
|
|
|
pp = p ? p->qid.path : 0;
|
|
h = (pp*333 + id*7 + t) & (nelem(uqidcache) - 1);
|
|
c = &uqidcache[h];
|
|
u = c->cache;
|
|
for(i=0; i <c->n ; i++){
|
|
if(u->ppath == pp && u->oid == id && u->t == t && u->idx == idx)
|
|
return (u->uqid << 8) | t;
|
|
u++;
|
|
}
|
|
if(c->n == c->max){
|
|
c->max += c->max/2 + 1;
|
|
c->cache = erealloc(c->cache, c->max*sizeof(Uqid));
|
|
}
|
|
nextqid++;
|
|
c->cache[c->n] = (Uqid){nextqid, pp, id, t, idx};
|
|
c->n++;
|
|
return (nextqid << 8) | t;
|
|
}
|
|
|
|
static Crumb*
|
|
crumb(Gitaux *aux, int n)
|
|
{
|
|
if(n < aux->ncrumb)
|
|
return &aux->crumb[aux->ncrumb - n - 1];
|
|
return nil;
|
|
}
|
|
|
|
static void
|
|
popcrumb(Gitaux *aux)
|
|
{
|
|
Crumb *c;
|
|
|
|
if(aux->ncrumb > 1){
|
|
c = crumb(aux, 0);
|
|
free(c->name);
|
|
unref(c->obj);
|
|
aux->ncrumb--;
|
|
}
|
|
}
|
|
|
|
static vlong
|
|
branchid(Gitaux *aux, char *path)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; branches[i]; i++)
|
|
if(strcmp(path, branches[i]) == 0)
|
|
goto found;
|
|
branches = realloc(branches, sizeof(char *)*(i + 2));
|
|
branches[i] = estrdup(path);
|
|
branches[i + 1] = nil;
|
|
|
|
found:
|
|
if(aux){
|
|
if(aux->refpath)
|
|
free(aux->refpath);
|
|
aux->refpath = estrdup(branches[i]);
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static void
|
|
obj2dir(Dir *d, Crumb *c, Object *o, char *name)
|
|
{
|
|
d->qid = c->qid;
|
|
d->atime = c->mtime;
|
|
d->mtime = c->mtime;
|
|
d->mode = c->mode;
|
|
d->name = estrdup9p(name);
|
|
d->uid = estrdup9p(username);
|
|
d->gid = estrdup9p(groupname);
|
|
d->muid = estrdup9p(username);
|
|
if(o->type == GBlob || o->type == GTag){
|
|
d->qid.type = 0;
|
|
d->mode &= 0777;
|
|
d->length = o->size;
|
|
}
|
|
|
|
}
|
|
|
|
static int
|
|
rootgen(int i, Dir *d, void *p)
|
|
{
|
|
Crumb *c;
|
|
|
|
c = crumb(p, 0);
|
|
if (i >= nelem(qroot))
|
|
return -1;
|
|
d->mode = 0555 | DMDIR;
|
|
d->name = estrdup9p(qroot[i]);
|
|
d->qid.vers = 0;
|
|
d->qid.type = strcmp(qroot[i], "ctl") == 0 ? 0 : QTDIR;
|
|
d->qid.path = qpath(nil, i, i, Qroot);
|
|
d->uid = estrdup9p(username);
|
|
d->gid = estrdup9p(groupname);
|
|
d->muid = estrdup9p(username);
|
|
d->mtime = c->mtime;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
branchgen(int i, Dir *d, void *p)
|
|
{
|
|
Gitaux *aux;
|
|
Dir *refs;
|
|
Crumb *c;
|
|
int n;
|
|
|
|
aux = p;
|
|
c = crumb(aux, 0);
|
|
refs = nil;
|
|
d->qid.vers = 0;
|
|
d->qid.type = QTDIR;
|
|
d->qid.path = qpath(c, i, branchid(aux, aux->refpath), Qbranch | Internal);
|
|
d->mode = 0555 | DMDIR;
|
|
d->uid = estrdup9p(username);
|
|
d->gid = estrdup9p(groupname);
|
|
d->muid = estrdup9p(username);
|
|
d->mtime = c->mtime;
|
|
d->atime = c->mtime;
|
|
if((n = slurpdir(aux->refpath, &refs)) < 0)
|
|
return -1;
|
|
if(i < n){
|
|
d->name = estrdup9p(refs[i].name);
|
|
free(refs);
|
|
return 0;
|
|
}else{
|
|
free(refs);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int
|
|
gtreegen(int i, Dir *d, void *p)
|
|
{
|
|
Object *o, *l, *e;
|
|
Gitaux *aux;
|
|
Crumb *c;
|
|
int m;
|
|
|
|
aux = p;
|
|
c = crumb(aux, 0);
|
|
e = c->obj;
|
|
if(i >= e->tree->nent)
|
|
return -1;
|
|
m = e->tree->ent[i].mode;
|
|
if(e->tree->ent[i].ismod)
|
|
o = emptydir();
|
|
else if((o = readobject(e->tree->ent[i].h)) == nil)
|
|
sysfatal("could not read object %H: %r", e->tree->ent[i].h);
|
|
if(e->tree->ent[i].islink)
|
|
if((l = walklink(aux, o->data, o->size, 0, &m)) != nil)
|
|
o = l;
|
|
d->qid.vers = 0;
|
|
d->qid.type = o->type == GTree ? QTDIR : 0;
|
|
d->qid.path = qpath(c, i, o->id, aux->qdir);
|
|
d->mode = m;
|
|
d->atime = c->mtime;
|
|
d->mtime = c->mtime;
|
|
d->uid = estrdup9p(username);
|
|
d->gid = estrdup9p(groupname);
|
|
d->muid = estrdup9p(username);
|
|
d->name = estrdup9p(e->tree->ent[i].name);
|
|
d->length = o->size;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
gcommitgen(int i, Dir *d, void *p)
|
|
{
|
|
Object *o;
|
|
Crumb *c;
|
|
|
|
c = crumb(p, 0);
|
|
o = c->obj;
|
|
d->uid = estrdup9p(username);
|
|
d->gid = estrdup9p(groupname);
|
|
d->muid = estrdup9p(username);
|
|
d->mode = 0444;
|
|
d->atime = o->commit->ctime;
|
|
d->mtime = o->commit->ctime;
|
|
d->qid.type = 0;
|
|
d->qid.vers = 0;
|
|
|
|
switch(i){
|
|
case 0:
|
|
d->mode = 0755 | DMDIR;
|
|
d->name = estrdup9p("tree");
|
|
d->qid.type = QTDIR;
|
|
d->qid.path = qpath(c, i, o->id, Qtree);
|
|
break;
|
|
case 1:
|
|
d->name = estrdup9p("parent");
|
|
d->qid.path = qpath(c, i, o->id, Qparent);
|
|
break;
|
|
case 2:
|
|
d->name = estrdup9p("msg");
|
|
d->qid.path = qpath(c, i, o->id, Qmsg);
|
|
break;
|
|
case 3:
|
|
d->name = estrdup9p("hash");
|
|
d->qid.path = qpath(c, i, o->id, Qhash);
|
|
break;
|
|
case 4:
|
|
d->name = estrdup9p("author");
|
|
d->qid.path = qpath(c, i, o->id, Qauthor);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
objgen(int i, Dir *d, void *p)
|
|
{
|
|
Gitaux *aux;
|
|
Object *o;
|
|
Crumb *c;
|
|
char name[64];
|
|
Objlist *ols;
|
|
Hash h;
|
|
|
|
aux = p;
|
|
c = crumb(aux, 0);
|
|
if(!aux->ols)
|
|
aux->ols = mkols();
|
|
ols = aux->ols;
|
|
o = nil;
|
|
/* We tried to sent it, but it didn't fit */
|
|
if(aux->olslast && ols->idx == i + 1){
|
|
snprint(name, sizeof(name), "%H", aux->olslast->hash);
|
|
obj2dir(d, c, aux->olslast, name);
|
|
return 0;
|
|
}
|
|
while(ols->idx <= i){
|
|
if(olsnext(ols, &h) == -1)
|
|
return -1;
|
|
if((o = readobject(h)) == nil){
|
|
fprint(2, "corrupt object %H\n", h);
|
|
return -1;
|
|
}
|
|
}
|
|
if(o != nil){
|
|
snprint(name, sizeof(name), "%H", o->hash);
|
|
obj2dir(d, c, o, name);
|
|
unref(aux->olslast);
|
|
aux->olslast = ref(o);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
objread(Req *r, Gitaux *aux)
|
|
{
|
|
Object *o;
|
|
|
|
o = crumb(aux, 0)->obj;
|
|
switch(o->type){
|
|
case GBlob:
|
|
readbuf(r, o->data, o->size);
|
|
break;
|
|
case GTag:
|
|
readbuf(r, o->data, o->size);
|
|
break;
|
|
case GTree:
|
|
dirread9p(r, gtreegen, aux);
|
|
break;
|
|
case GCommit:
|
|
dirread9p(r, gcommitgen, aux);
|
|
break;
|
|
default:
|
|
sysfatal("invalid object type %d", o->type);
|
|
}
|
|
}
|
|
|
|
static void
|
|
readcommitparent(Req *r, Object *o)
|
|
{
|
|
char *buf, *p, *e;
|
|
int i, n;
|
|
|
|
/* 40 bytes per hash, 1 per nl, 1 for terminator */
|
|
n = o->commit->nparent * (40 + 1) + 1;
|
|
buf = emalloc(n);
|
|
p = buf;
|
|
e = buf + n;
|
|
for (i = 0; i < o->commit->nparent; i++)
|
|
p = seprint(p, e, "%H\n", o->commit->parent[i]);
|
|
readbuf(r, buf, p - buf);
|
|
free(buf);
|
|
}
|
|
|
|
static void
|
|
gitattach(Req *r)
|
|
{
|
|
Gitaux *aux;
|
|
Dir *d;
|
|
|
|
if((d = dirstat(".git")) == nil)
|
|
sysfatal("git/fs: %r");
|
|
if(getwd(gitdir, sizeof(gitdir)) == nil)
|
|
sysfatal("getwd: %r");
|
|
aux = emalloc(sizeof(Gitaux));
|
|
aux->crumb = emalloc(sizeof(Crumb));
|
|
aux->crumb[0].qid = (Qid){Qroot, 0, QTDIR};
|
|
aux->crumb[0].obj = nil;
|
|
aux->crumb[0].mode = DMDIR | 0555;
|
|
aux->crumb[0].mtime = d->mtime;
|
|
aux->crumb[0].name = estrdup("/");
|
|
aux->ncrumb = 1;
|
|
r->ofcall.qid = (Qid){Qroot, 0, QTDIR};
|
|
r->fid->qid = r->ofcall.qid;
|
|
r->fid->aux = aux;
|
|
respond(r, nil);
|
|
}
|
|
|
|
static Object*
|
|
walklink(Gitaux *aux, char *link, int nlink, int ndotdot, int *mode)
|
|
{
|
|
char *p, *e, *path;
|
|
Object *o, *n;
|
|
int i;
|
|
|
|
path = emalloc(nlink + 1);
|
|
memcpy(path, link, nlink);
|
|
cleanname(path);
|
|
|
|
o = crumb(aux, ndotdot)->obj;
|
|
assert(o->type == GTree);
|
|
for(p = path; *p; p = e){
|
|
n = nil;
|
|
e = p + strcspn(p, "/");
|
|
if(*e == '/')
|
|
*e++ = '\0';
|
|
/*
|
|
* cleanname guarantees these show up at the start of the name,
|
|
* which allows trimming them from the end of the trail of crumbs
|
|
* instead of needing to keep track of full parentage.
|
|
*/
|
|
if(strcmp(p, "..") == 0)
|
|
n = crumb(aux, ++ndotdot)->obj;
|
|
else if(o->type == GTree)
|
|
for(i = 0; i < o->tree->nent; i++)
|
|
if(strcmp(o->tree->ent[i].name, p) == 0){
|
|
*mode = o->tree->ent[i].mode;
|
|
n = readobject(o->tree->ent[i].h);
|
|
break;
|
|
}
|
|
o = n;
|
|
if(o == nil)
|
|
break;
|
|
}
|
|
free(path);
|
|
return o;
|
|
}
|
|
|
|
static char *
|
|
objwalk1(Qid *q, Object *o, Crumb *p, Crumb *c, char *name, vlong qdir, Gitaux *aux)
|
|
{
|
|
Object *w, *l;
|
|
char *e;
|
|
int i, m;
|
|
|
|
w = nil;
|
|
e = nil;
|
|
if(!o)
|
|
return Eexist;
|
|
if(o->type == GTree){
|
|
q->type = 0;
|
|
for(i = 0; i < o->tree->nent; i++){
|
|
if(strcmp(o->tree->ent[i].name, name) != 0)
|
|
continue;
|
|
m = o->tree->ent[i].mode;
|
|
w = readobject(o->tree->ent[i].h);
|
|
if(!w && o->tree->ent[i].ismod)
|
|
w = emptydir();
|
|
if(w && o->tree->ent[i].islink)
|
|
if((l = walklink(aux, w->data, w->size, 1, &m)) != nil)
|
|
w = l;
|
|
if(!w)
|
|
return Ebadobj;
|
|
q->type = (w->type == GTree) ? QTDIR : 0;
|
|
q->path = qpath(c, i, w->id, qdir);
|
|
c->mode = m;
|
|
c->mode |= (w->type == GTree) ? DMDIR|0755 : 0644;
|
|
c->obj = w;
|
|
break;
|
|
}
|
|
if(!w)
|
|
e = Eexist;
|
|
}else if(o->type == GCommit){
|
|
q->type = 0;
|
|
c->mtime = o->commit->mtime;
|
|
c->mode = 0644;
|
|
assert(qdir == Qcommit || qdir == Qobject || qdir == Qtree || qdir == Qhead || qdir == Qcommitter);
|
|
if(strcmp(name, "msg") == 0)
|
|
q->path = qpath(p, 0, o->id, Qmsg);
|
|
else if(strcmp(name, "parent") == 0)
|
|
q->path = qpath(p, 1, o->id, Qparent);
|
|
else if(strcmp(name, "hash") == 0)
|
|
q->path = qpath(p, 2, o->id, Qhash);
|
|
else if(strcmp(name, "author") == 0)
|
|
q->path = qpath(p, 3, o->id, Qauthor);
|
|
else if(strcmp(name, "committer") == 0)
|
|
q->path = qpath(p, 3, o->id, Qcommitter);
|
|
else if(strcmp(name, "tree") == 0){
|
|
q->type = QTDIR;
|
|
q->path = qpath(p, 4, o->id, Qtree);
|
|
unref(c->obj);
|
|
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;
|
|
}else if(o->type == GTag){
|
|
e = "tag walk unimplemented";
|
|
}
|
|
return e;
|
|
}
|
|
|
|
static Object *
|
|
readref(char *pathstr)
|
|
{
|
|
char buf[128], path[128], *p, *e;
|
|
Hash h;
|
|
int n, f;
|
|
|
|
snprint(path, sizeof(path), "%s", pathstr);
|
|
while(1){
|
|
if((f = open(path, OREAD)) == -1)
|
|
return nil;
|
|
if((n = readn(f, buf, sizeof(buf) - 1)) == -1)
|
|
return nil;
|
|
close(f);
|
|
buf[n] = 0;
|
|
if(strncmp(buf, "ref:", 4) != 0)
|
|
break;
|
|
|
|
p = buf + 4;
|
|
while(isspace(*p))
|
|
p++;
|
|
if((e = strchr(p, '\n')) != nil)
|
|
*e = 0;
|
|
snprint(path, sizeof(path), ".git/%s", p);
|
|
}
|
|
|
|
if(hparse(&h, buf) == -1)
|
|
return nil;
|
|
|
|
return readobject(h);
|
|
}
|
|
|
|
static char*
|
|
gitwalk1(Fid *fid, char *name, Qid *q)
|
|
{
|
|
char path[128];
|
|
Gitaux *aux;
|
|
Crumb *c, *o;
|
|
char *e;
|
|
Dir *d;
|
|
Hash h;
|
|
|
|
e = nil;
|
|
aux = fid->aux;
|
|
|
|
q->vers = 0;
|
|
if(strcmp(name, "..") == 0){
|
|
popcrumb(aux);
|
|
c = crumb(aux, 0);
|
|
*q = c->qid;
|
|
fid->qid = *q;
|
|
return nil;
|
|
}
|
|
|
|
aux->crumb = realloc(aux->crumb, (aux->ncrumb + 1) * sizeof(Crumb));
|
|
aux->ncrumb++;
|
|
c = crumb(aux, 0);
|
|
o = crumb(aux, 1);
|
|
memset(c, 0, sizeof(Crumb));
|
|
c->mode = o->mode;
|
|
c->mtime = o->mtime;
|
|
c->obj = o->obj ? ref(o->obj) : nil;
|
|
|
|
switch(QDIR(&fid->qid)){
|
|
case Qroot:
|
|
if(strcmp(name, "HEAD") == 0){
|
|
*q = (Qid){Qhead, 0, QTDIR};
|
|
c->mode = DMDIR | 0555;
|
|
c->obj = readref(".git/HEAD");
|
|
}else if(strcmp(name, "object") == 0){
|
|
*q = (Qid){Qobject, 0, QTDIR};
|
|
c->mode = DMDIR | 0555;
|
|
}else if(strcmp(name, "branch") == 0){
|
|
*q = (Qid){Qbranch, 0, QTDIR};
|
|
aux->refpath = estrdup(".git/refs/");
|
|
c->mode = DMDIR | 0555;
|
|
}else if(strcmp(name, "ctl") == 0){
|
|
*q = (Qid){Qctl, 0, 0};
|
|
c->mode = 0644;
|
|
}else{
|
|
e = Eexist;
|
|
}
|
|
break;
|
|
case Qbranch:
|
|
if(strcmp(aux->refpath, ".git/refs/heads") == 0 && strcmp(name, "HEAD") == 0)
|
|
snprint(path, sizeof(path), ".git/HEAD");
|
|
else
|
|
snprint(path, sizeof(path), "%s/%s", aux->refpath, name);
|
|
q->type = QTDIR;
|
|
d = dirstat(path);
|
|
if(d && d->qid.type == QTDIR)
|
|
q->path = qpath(o, Qbranch, branchid(aux, path), Qbranch);
|
|
else if(d && (c->obj = readref(path)) != nil)
|
|
q->path = qpath(o, Qbranch, c->obj->id, Qcommit);
|
|
else
|
|
e = Eexist;
|
|
free(d);
|
|
break;
|
|
case Qobject:
|
|
if(c->obj){
|
|
e = objwalk1(q, o->obj, o, c, name, Qobject, aux);
|
|
}else{
|
|
if(hparse(&h, name) == -1)
|
|
return Eobject;
|
|
if((c->obj = readobject(h)) == nil)
|
|
return Eobject;
|
|
if(c->obj->type == GBlob || c->obj->type == GTag){
|
|
c->mode = 0644;
|
|
q->type = 0;
|
|
}else{
|
|
c->mode = DMDIR | 0755;
|
|
q->type = QTDIR;
|
|
}
|
|
q->path = qpath(o, Qobject, c->obj->id, Qobject);
|
|
q->vers = 0;
|
|
}
|
|
break;
|
|
case Qhead:
|
|
e = objwalk1(q, o->obj, o, c, name, Qhead, aux);
|
|
break;
|
|
case Qcommit:
|
|
e = objwalk1(q, o->obj, o, c, name, Qcommit, aux);
|
|
break;
|
|
case Qtree:
|
|
e = objwalk1(q, o->obj, o, c, name, Qtree, aux);
|
|
break;
|
|
case Qparent:
|
|
case Qmsg:
|
|
case Qcdata:
|
|
case Qhash:
|
|
case Qauthor:
|
|
case Qcommitter:
|
|
case Qctl:
|
|
return Enodir;
|
|
default:
|
|
return Egreg;
|
|
}
|
|
|
|
c->name = estrdup(name);
|
|
c->qid = *q;
|
|
fid->qid = *q;
|
|
return e;
|
|
}
|
|
|
|
static char*
|
|
gitclone(Fid *o, Fid *n)
|
|
{
|
|
Gitaux *aux, *oaux;
|
|
int i;
|
|
|
|
oaux = o->aux;
|
|
aux = emalloc(sizeof(Gitaux));
|
|
aux->ncrumb = oaux->ncrumb;
|
|
aux->crumb = eamalloc(oaux->ncrumb, sizeof(Crumb));
|
|
for(i = 0; i < aux->ncrumb; i++){
|
|
aux->crumb[i] = oaux->crumb[i];
|
|
aux->crumb[i].name = estrdup(oaux->crumb[i].name);
|
|
if(aux->crumb[i].obj)
|
|
aux->crumb[i].obj = ref(oaux->crumb[i].obj);
|
|
}
|
|
if(oaux->refpath)
|
|
aux->refpath = strdup(oaux->refpath);
|
|
aux->qdir = oaux->qdir;
|
|
n->aux = aux;
|
|
return nil;
|
|
}
|
|
|
|
static void
|
|
gitdestroyfid(Fid *f)
|
|
{
|
|
Gitaux *aux;
|
|
int i;
|
|
|
|
if((aux = f->aux) == nil)
|
|
return;
|
|
for(i = 0; i < aux->ncrumb; i++){
|
|
if(aux->crumb[i].obj)
|
|
unref(aux->crumb[i].obj);
|
|
free(aux->crumb[i].name);
|
|
}
|
|
olsfree(aux->ols);
|
|
free(aux->refpath);
|
|
free(aux->crumb);
|
|
free(aux);
|
|
}
|
|
|
|
static char *
|
|
readctl(Req *r)
|
|
{
|
|
char data[1024], ref[512], *s, *e;
|
|
int fd, n;
|
|
|
|
if((fd = open(".git/HEAD", OREAD)) == -1)
|
|
return Erepo;
|
|
/* empty HEAD is invalid */
|
|
if((n = readn(fd, ref, sizeof(ref) - 1)) <= 0)
|
|
return Erepo;
|
|
close(fd);
|
|
|
|
s = ref;
|
|
ref[n] = 0;
|
|
if(strncmp(s, "ref:", 4) == 0)
|
|
s += 4;
|
|
while(*s == ' ' || *s == '\t')
|
|
s++;
|
|
if((e = strchr(s, '\n')) != nil)
|
|
*e = 0;
|
|
if(strstr(s, "refs/") == s)
|
|
s += strlen("refs/");
|
|
|
|
snprint(data, sizeof(data), "branch %s\nrepo %s\n", s, gitdir);
|
|
readstr(r, data);
|
|
return nil;
|
|
}
|
|
|
|
static void
|
|
gitread(Req *r)
|
|
{
|
|
char buf[256], *e;
|
|
Gitaux *aux;
|
|
Object *o;
|
|
Qid *q;
|
|
|
|
aux = r->fid->aux;
|
|
q = &r->fid->qid;
|
|
o = crumb(aux, 0)->obj;
|
|
e = nil;
|
|
|
|
switch(QDIR(q)){
|
|
case Qroot:
|
|
dirread9p(r, rootgen, aux);
|
|
break;
|
|
case Qbranch:
|
|
if(o)
|
|
objread(r, aux);
|
|
else
|
|
dirread9p(r, branchgen, aux);
|
|
break;
|
|
case Qobject:
|
|
if(o)
|
|
objread(r, aux);
|
|
else
|
|
dirread9p(r, objgen, aux);
|
|
break;
|
|
case Qmsg:
|
|
readbuf(r, o->commit->msg, o->commit->nmsg);
|
|
break;
|
|
case Qparent:
|
|
readcommitparent(r, o);
|
|
break;
|
|
case Qhash:
|
|
snprint(buf, sizeof(buf), "%H\n", o->hash);
|
|
readstr(r, buf);
|
|
break;
|
|
case Qauthor:
|
|
snprint(buf, sizeof(buf), "%s\n", o->commit->author);
|
|
readstr(r, buf);
|
|
break;
|
|
case Qcommitter:
|
|
snprint(buf, sizeof(buf), "%s\n", o->commit->committer);
|
|
readstr(r, buf);
|
|
break;
|
|
case Qctl:
|
|
e = readctl(r);
|
|
break;
|
|
case Qhead:
|
|
/* Empty repositories have no HEAD */
|
|
if(o == nil)
|
|
r->ofcall.count = 0;
|
|
else
|
|
objread(r, aux);
|
|
break;
|
|
case Qcommit:
|
|
case Qtree:
|
|
case Qcdata:
|
|
objread(r, aux);
|
|
break;
|
|
default:
|
|
e = Egreg;
|
|
}
|
|
respond(r, e);
|
|
}
|
|
|
|
static void
|
|
gitopen(Req *r)
|
|
{
|
|
Gitaux *aux;
|
|
Crumb *c;
|
|
|
|
aux = r->fid->aux;
|
|
c = crumb(aux, 0);
|
|
switch(r->ifcall.mode&3){
|
|
default:
|
|
respond(r, "botched mode");
|
|
break;
|
|
case OWRITE:
|
|
respond(r, Eperm);
|
|
break;
|
|
case OREAD:
|
|
case ORDWR:
|
|
respond(r, nil);
|
|
break;
|
|
case OEXEC:
|
|
if((c->mode & 0111) == 0)
|
|
respond(r, Eperm);
|
|
else
|
|
respond(r, nil);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gitstat(Req *r)
|
|
{
|
|
Gitaux *aux;
|
|
Crumb *c;
|
|
|
|
aux = r->fid->aux;
|
|
c = crumb(aux, 0);
|
|
r->d.uid = estrdup9p(username);
|
|
r->d.gid = estrdup9p(groupname);
|
|
r->d.muid = estrdup9p(username);
|
|
r->d.qid = r->fid->qid;
|
|
r->d.mtime = c->mtime;
|
|
r->d.atime = c->mtime;
|
|
r->d.mode = c->mode;
|
|
if(c->obj)
|
|
obj2dir(&r->d, c, c->obj, c->name);
|
|
else
|
|
r->d.name = estrdup9p(c->name);
|
|
respond(r, nil);
|
|
}
|
|
|
|
Srv gitsrv = {
|
|
.attach=gitattach,
|
|
.walk1=gitwalk1,
|
|
.clone=gitclone,
|
|
.open=gitopen,
|
|
.read=gitread,
|
|
.stat=gitstat,
|
|
.destroyfid=gitdestroyfid,
|
|
};
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprint(2, "usage: %s [-d]\n", argv0);
|
|
fprint(2, "\t-d: debug\n");
|
|
exits("usage");
|
|
}
|
|
|
|
void
|
|
main(int argc, char **argv)
|
|
{
|
|
Dir *d;
|
|
|
|
gitinit();
|
|
ARGBEGIN{
|
|
case 'd':
|
|
chatty9p++;
|
|
break;
|
|
case 'm':
|
|
mntpt = EARGF(usage());
|
|
break;
|
|
default:
|
|
usage();
|
|
break;
|
|
}ARGEND;
|
|
if(argc != 0)
|
|
usage();
|
|
|
|
if((d = dirstat(".git")) == nil)
|
|
sysfatal("dirstat .git: %r");
|
|
username = strdup(d->uid);
|
|
groupname = strdup(d->gid);
|
|
free(d);
|
|
|
|
branches = emalloc(sizeof(char*));
|
|
branches[0] = nil;
|
|
postmountsrv(&gitsrv, nil, mntpt, MCREATE);
|
|
exits(nil);
|
|
}
|