plan9fox/sys/src/cmd/git/log.c
Ori Bernstein 21aac62c1f git/log: support -n option to restrict log counts
this is useful for scripting, and convenient for
interactive use.
2022-07-03 04:38:13 +00:00

289 lines
5.2 KiB
C

#include <u.h>
#include <libc.h>
#include "git.h"
typedef struct Pfilt Pfilt;
struct Pfilt {
char *elt;
int show;
Pfilt *sub;
int nsub;
};
Biobuf *out;
char *queryexpr;
char *commitid;
int shortlog;
int msgcount;
Objset done;
Objq objq;
Pfilt *pathfilt;
void
filteradd(Pfilt *pf, char *path)
{
char *p, *e;
int i;
if((e = strchr(path, '/')) != nil)
p = smprint("%.*s", (int)(e - path), path);
else
p = strdup(path);
while(e != nil && *e == '/')
e++;
for(i = 0; i < pf->nsub; i++){
if(strcmp(pf->sub[i].elt, p) == 0){
pf->sub[i].show = pf->sub[i].show || (e == nil);
if(e != nil)
filteradd(&pf->sub[i], e);
free(p);
return;
}
}
pf->sub = earealloc(pf->sub, pf->nsub+1, sizeof(Pfilt));
pf->sub[pf->nsub].elt = p;
pf->sub[pf->nsub].show = (e == nil);
pf->sub[pf->nsub].nsub = 0;
pf->sub[pf->nsub].sub = nil;
if(e != nil)
filteradd(&pf->sub[pf->nsub], e);
pf->nsub++;
}
Hash
lookup(Pfilt *pf, Object *o)
{
int i;
for(i = 0; i < o->tree->nent; i++)
if(strcmp(o->tree->ent[i].name, pf->elt) == 0)
return o->tree->ent[i].h;
return Zhash;
}
int
filtermatch1(Pfilt *pf, Object *t, Object *pt)
{
Object *a, *b;
Hash ha, hb;
int i, r;
if(pf->show)
return 1;
if(t->type != pt->type)
return 1;
if(t->type != GTree)
return 0;
for(i = 0; i < pf->nsub; i++){
ha = lookup(&pf->sub[i], t);
hb = lookup(&pf->sub[i], pt);
if(hasheq(&ha, &hb))
continue;
if(hasheq(&ha, &Zhash) || hasheq(&hb, &Zhash))
return 1;
if((a = readobject(ha)) == nil)
sysfatal("read %H: %r", ha);
if((b = readobject(hb)) == nil)
sysfatal("read %H: %r", hb);
r = filtermatch1(&pf->sub[i], a, b);
unref(a);
unref(b);
if(r)
return 1;
}
return 0;
}
int
filtermatch(Object *o)
{
Object *t, *p, *pt;
int i, r;
if(pathfilt == nil)
return 1;
if((t = readobject(o->commit->tree)) == nil)
sysfatal("read %H: %r", o->commit->tree);
for(i = 0; i < o->commit->nparent; i++){
if((p = readobject(o->commit->parent[i])) == nil)
sysfatal("read %H: %r", o->commit->parent[i]);
if((pt = readobject(p->commit->tree)) == nil)
sysfatal("read %H: %r", o->commit->tree);
r = filtermatch1(pathfilt, t, pt);
unref(p);
unref(pt);
if(r)
return 1;
}
return o->commit->nparent == 0;
}
static char*
nextline(char *p, char *e)
{
for(; p != e; p++)
if(*p == '\n')
break;
return p;
}
static void
show(Object *o)
{
Tm tm;
char *p, *q, *e;
assert(o->type == GCommit);
if(!filtermatch(o))
return;
if(shortlog){
p = o->commit->msg;
e = p + o->commit->nmsg;
q = nextline(p, e);
Bprint(out, "%H ", o->hash);
Bwrite(out, p, q - p);
Bputc(out, '\n');
}else{
tmtime(&tm, o->commit->mtime, tzload("local"));
Bprint(out, "Hash:\t%H\n", o->hash);
Bprint(out, "Author:\t%s\n", o->commit->author);
if(o->commit->committer != nil
&& strcmp(o->commit->author, o->commit->committer) != 0)
Bprint(out, "Committer:\t%s\n", o->commit->committer);
Bprint(out, "Date:\t\n", tmfmt(&tm, "WW MMM D hh:mm:ss z YYYY"));
Bprint(out, "\n");
p = o->commit->msg;
e = p + o->commit->nmsg;
for(; p != e; p = q){
q = nextline(p, e);
Bputc(out, '\t');
Bwrite(out, p, q - p);
Bputc(out, '\n');
if(q != e)
q++;
}
Bprint(out, "\n");
}
Bflush(out);
}
static void
showquery(char *q)
{
Object *o;
Hash *h;
int n, i;
if((n = resolverefs(&h, q)) == -1)
sysfatal("resolve: %r");
for(i = 0; i < n && msgcount-- > 0; i++){
if((o = readobject(h[i])) == nil)
sysfatal("read %H: %r", h[i]);
show(o);
unref(o);
}
exits(nil);
}
static void
showcommits(char *c)
{
Object *o, *p;
Qelt e;
int i;
Hash h;
if(c == nil)
c = "HEAD";
if(resolveref(&h, c) == -1)
sysfatal("resolve %s: %r", c);
if((o = readobject(h)) == nil)
sysfatal("load %H: %r", h);
qinit(&objq);
osinit(&done);
qput(&objq, o, 0);
while(qpop(&objq, &e) && msgcount-- > 0){
show(e.o);
for(i = 0; i < e.o->commit->nparent; i++){
if(oshas(&done, e.o->commit->parent[i]))
continue;
if((p = readobject(e.o->commit->parent[i])) == nil)
sysfatal("load %H: %r", o->commit->parent[i]);
osadd(&done, p);
qput(&objq, p, 0);
}
unref(e.o);
}
}
static void
usage(void)
{
fprint(2, "usage: %s [-s] [-e expr | -c commit] files..\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
char path[1024], repo[1024], *p, *r;
int i, nrepo;
ARGBEGIN{
case 'e':
queryexpr = EARGF(usage());
break;
case 'c':
commitid = EARGF(usage());
break;
case 's':
shortlog++;
break;
case 'n':
msgcount = atoi(EARGF(usage()));
break;
default:
usage();
break;
}ARGEND;
if(findrepo(repo, sizeof(repo)) == -1)
sysfatal("find root: %r");
nrepo = strlen(repo);
if(argc != 0){
if(getwd(path, sizeof(path)) == nil)
sysfatal("getwd: %r");
if(strncmp(path, repo, nrepo) != 0)
sysfatal("path shifted??");
p = path + nrepo;
pathfilt = emalloc(sizeof(Pfilt));
for(i = 0; i < argc; i++){
if(*argv[i] == '/'){
if(strncmp(argv[i], repo, nrepo) != 0)
continue;
r = smprint("./%s", argv[i]+nrepo);
}else
r = smprint("./%s/%s", p, argv[i]);
cleanname(r);
filteradd(pathfilt, r);
free(r);
}
}
if(chdir(repo) == -1)
sysfatal("chdir: %r");
gitinit();
tmfmtinstall();
out = Bfdopen(1, OWRITE);
if(queryexpr != nil)
showquery(queryexpr);
else
showcommits(commitid);
exits(nil);
}