plan9fox/sys/src/cmd/git/query.c
Ori Bernstein c7dcc82b0b git/query: fix spurious merge requests
Due to the way LCA is defined, a using a strict LCA
on a graph like this:

 <--a--b--c--d--e--f--g
     \               /
       +-----h-------

can lead to spurious requests to merge. This happens
because 'lca(b, g)' would return 'a', since it can be
reached in one step from 'b', and 2 steps from 'g', while
reaching 'b' from 'a' would be a longer path.

As a result, we need to implement an lca variant that
returns the starting node if one is reachable from the
other, even if it's already found the technically correct
least common ancestor.

This replaces our LCA algorithm with one based on the
painting we do while finding a twixt, making it give
the resutls we want.
git/query: fix spurious merge requests

Due to the way LCA is defined, a using a strict LCA
on a graph like this:

 <--a--b--c--d--e--f--g
     \               /
       +-----h-------

can lead to spurious requests to merge. This happens
because 'lca(b, g)' would return 'a', since it can be
reached in one step from 'b', and 2 steps from 'g', while
reaching 'b' from 'a' would be a longer path.

As a result, we need to implement an lca variant that
returns the starting node if one is reachable from the
other, even if it's already found the technically correct
least common ancestor.

This replaces our LCA algorithm with one based on the
painting we do while finding a twixt.
2021-09-11 17:46:26 +00:00

200 lines
3.6 KiB
C

#include <u.h>
#include <libc.h>
#include "git.h"
#pragma varargck type "P" void
int fullpath;
int changes;
int reverse;
char *path[128];
int npath;
int
Pfmt(Fmt *f)
{
int i, n;
n = 0;
for(i = 0; i < npath; i++)
n += fmtprint(f, "%s/", path[i]);
return n;
}
void
showdir(Hash dh, char *dname, char m)
{
Dirent *p, *e;
Object *d;
path[npath++] = dname;
if((d = readobject(dh)) == nil)
sysfatal("bad hash %H", dh);
assert(d->type == GTree);
p = d->tree->ent;
e = p + d->tree->nent;
for(; p != e; p++){
if(p->ismod)
continue;
if(p->mode & DMDIR)
showdir(p->h, p->name, m);
else
print("%c %P%s\n", m, p->name);
}
print("%c %P\n", m);
unref(d);
npath--;
}
void
show(Dirent *e, char m)
{
if(e->mode & DMDIR)
showdir(e->h, e->name, m);
else
print("%c %P%s\n", m, e->name);
}
void
difftrees(Object *a, Object *b)
{
Dirent *ap, *bp, *ae, *be;
int c;
ap = ae = nil;
bp = be = nil;
if(a != nil){
if(a->type != GTree)
return;
ap = a->tree->ent;
ae = ap + a->tree->nent;
}
if(b != nil){
if(b->type != GTree)
return;
bp = b->tree->ent;
be = bp + b->tree->nent;
}
while(ap != ae && bp != be){
c = strcmp(ap->name, bp->name);
if(c == 0){
if(ap->mode == bp->mode && hasheq(&ap->h, &bp->h))
goto next;
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)){
if(npath >= nelem(path))
sysfatal("path too deep");
path[npath++] = ap->name;
if((a = readobject(ap->h)) == nil)
sysfatal("bad hash %H", ap->h);
if((b = readobject(bp->h)) == nil)
sysfatal("bad hash %H", bp->h);
difftrees(a, b);
unref(a);
unref(b);
npath--;
}
next:
ap++;
bp++;
}else if(c < 0) {
show(ap, '-');
ap++;
}else if(c > 0){
show(bp, '+');
bp++;
}
}
for(; ap != ae; ap++)
show(ap, '-');
for(; bp != be; bp++)
show(bp, '+');
}
void
diffcommits(Hash ah, Hash bh)
{
Object *a, *b, *at, *bt;
at = nil;
bt = nil;
if(!hasheq(&ah, &Zhash) && (a = readobject(ah)) != nil){
if(a->type != GCommit)
sysfatal("not commit: %H", ah);
if((at = readobject(a->commit->tree)) == nil)
sysfatal("bad hash %H", a->commit->tree);
unref(a);
}
if(!hasheq(&bh, &Zhash) && (b = readobject(bh)) != nil){
if(b->type != GCommit)
sysfatal("not commit: %H", ah);
if((bt = readobject(b->commit->tree)) == nil)
sysfatal("bad hash %H", b->commit->tree);
unref(b);
}
difftrees(at, bt);
unref(at);
unref(bt);
}
void
usage(void)
{
fprint(2, "usage: %s [-pcr] query\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
int i, j, n;
Hash *h;
char *p, *e, *s, *objpfx;
char query[2048], repo[512];
ARGBEGIN{
case 'd': chattygit++; break;
case 'p': fullpath++; break;
case 'c': changes++; break;
case 'r': reverse ^= 1; break;
default: usage(); break;
}ARGEND;
gitinit();
fmtinstall('P', Pfmt);
if(argc == 0)
usage();
if(findrepo(repo, sizeof(repo)) == -1)
sysfatal("find root: %r");
if(chdir(repo) == -1)
sysfatal("chdir: %r");
if((objpfx = smprint("%s/.git/fs/object/", repo)) == nil)
sysfatal("smprint: %r");
s = "";
p = query;
e = query + nelem(query);
for(i = 0; i < argc; i++){
p = seprint(p, e, "%s%s", s, argv[i]);
s = " ";
}
if((n = resolverefs(&h, query)) == -1)
sysfatal("resolve: %r");
if(changes){
if(n != 2)
sysfatal("diff: need 2 commits, got %d", n);
diffcommits(h[0], h[1]);
}else{
p = (fullpath ? objpfx : "");
for(j = 0; j < n; j++)
print("%s%H\n", p, h[reverse ? n - 1 - j : j]);
}
exits(nil);
}