patch: implement a new, simpler patch program to replace ape/patch

ape/patch is a giant, ugly ball of code from almost 25 years ago,
which has not and will likely never been updated or maintained.

the world has since settled on unified diffs, and we just need a
simple program that can parse and apply them.
This commit is contained in:
Ori Bernstein 2022-06-04 01:56:01 +00:00
parent 761bf6c347
commit 07e1620911

611
sys/src/cmd/patch.c Normal file
View file

@ -0,0 +1,611 @@
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <bio.h>
typedef struct Patch Patch;
typedef struct Hunk Hunk;
typedef struct Fbuf Fbuf;
struct Patch {
char *name;
Hunk *hunk;
usize nhunk;
};
struct Hunk {
int lnum;
char *oldpath;
int oldln;
int oldcnt;
int oldlen;
int oldsz;
char *old;
char *newpath;
int newln;
int newcnt;
int newlen;
int newsz;
char *new;
};
struct Fbuf {
int *lines;
int nlines;
int lastln;
char *buf;
int len;
};
int strip;
int reverse;
void (*addnew)(Hunk*, char*);
void (*addold)(Hunk*, char*);
char*
readline(Biobuf *f, int *lnum)
{
char *ln;
if((ln = Brdstr(f, '\n', 0)) == nil)
return nil;
*lnum += 1;
return ln;
}
void *
emalloc(ulong n)
{
void *v;
v = mallocz(n, 1);
if(v == nil)
sysfatal("malloc: %r");
setmalloctag(v, getcallerpc(&n));
return v;
}
void *
erealloc(void *v, ulong n)
{
if(n == 0)
n++;
v = realloc(v, n);
if(v == nil)
sysfatal("malloc: %r");
setmalloctag(v, getcallerpc(&n));
return v;
}
int
fileheader(char *s, char *pfx, char **name)
{
int len, n, nnull;
char *e;
if((strncmp(s, pfx, strlen(pfx))) != 0)
return -1;
for(s += strlen(pfx); *s; s++)
if(!isspace(*s))
break;
for(e = s; *e; e++)
if(isspace(*e))
break;
if(s == e)
return -1;
nnull = strlen("/dev/null");
if((e - s) != nnull || strncmp(s, "/dev/null", nnull) != 0){
n = strip;
while(s != e && n > 0){
while(s != e && *s == '/')
s++;
while(s != e && *s != '/')
s++;
n--;
}
while(*s == '/')
s++;
if(*s == '\0')
sysfatal("too many components stripped");
}
len = (e - s) + 1;
*name = emalloc(len);
strecpy(*name, *name + len, s);
return 0;
}
int
hunkheader(Hunk *h, char *s, char *oldpath, char *newpath, int lnum)
{
char *e;
memset(h, 0, sizeof(*h));
h->lnum = lnum;
h->oldpath = strdup(oldpath);
h->newpath = strdup(newpath);
h->oldlen = 0;
h->oldsz = 32;
h->old = emalloc(h->oldsz);
h->newlen = 0;
h->newsz = 32;
h->new = emalloc(h->newsz);
if(strncmp(s, "@@ -", 4) != 0)
return -1;
e = s + 4;
h->oldln = strtol(e, &e, 10);
h->oldcnt = 1;
if(*e == ','){
e++;
h->oldcnt = strtol(e, &e, 10);
}
while(*e == ' ' || *e == '\t')
e++;
if(*e != '+')
return -1;
e++;
h->newln = strtol(e, &e, 10);
if(e == s)
return -1;
h->newcnt = 1;
if(*e == ','){
e++;
h->newcnt = strtol(e, &e, 10);
}
if(e == s || *e != ' ')
return -1;
if(strncmp(e, " @@", 3) != 0)
return -1;
/*
* empty files have line number 0: keep that,
* otherwise adjust down.
*/
if(h->oldln > 0)
h->oldln--;
if(h->newln > 0)
h->newln--;
if(h->oldln < 0 || h->newln < 0 || h->oldcnt < 0 || h->newcnt < 0)
sysfatal("malformed hunk %s", s);
return 0;
}
void
addnewfn(Hunk *h, char *ln)
{
int n;
ln++;
n = strlen(ln);
while(h->newlen + n >= h->newsz){
h->newsz *= 2;
h->new = erealloc(h->new, h->newsz);
}
memcpy(h->new + h->newlen, ln, n);
h->newlen += n;
}
void
addoldfn(Hunk *h, char *ln)
{
int n;
ln++;
n = strlen(ln);
while(h->oldlen + n >= h->oldsz){
h->oldsz *= 2;
h->old = erealloc(h->old, h->oldsz);
}
memcpy(h->old + h->oldlen, ln, n);
h->oldlen += n;
}
int
addmiss(Hunk *h, char *ln, int *nold, int *nnew)
{
if(ln == nil)
return 1;
else if(ln[0] != '-' && ln[0] != '+')
return 0;
if(ln[0] == '-'){
addold(h, ln);
*nold += 1;
}else{
addnew(h, ln);
*nnew += 1;
}
return 1;
}
void
addhunk(Patch *p, Hunk *h)
{
p->hunk = erealloc(p->hunk, ++p->nhunk*sizeof(Hunk));
p->hunk[p->nhunk-1] = *h;
}
int
hunkcmp(void *a, void *b)
{
int c;
c = strcmp(((Hunk*)a)->oldpath, ((Hunk*)b)->oldpath);
if(c != 0)
return c;
return ((Hunk*)a)->oldln - ((Hunk*)b)->oldln;
}
Patch*
parse(Biobuf *f, char *name)
{
char *ln, *old, *new, **oldp, **newp;
int oldcnt, newcnt, lnum;
Patch *p;
Hunk h;
ln = nil;
lnum = 0;
p = emalloc(sizeof(Patch));
if(!reverse){
oldp = &old;
newp = &new;
}else{
oldp = &new;
newp = &old;
}
comment:
free(ln);
while((ln = readline(f, &lnum)) != nil){
if(strncmp(ln, "--- ", 4) == 0)
goto patch;
free(ln);
}
if(p->nhunk == 0)
sysfatal("%s: could not find start of patch", name);
goto out;
patch:
if(fileheader(ln, "--- ", oldp) == -1)
goto comment;
free(ln);
if((ln = readline(f, &lnum)) == nil)
goto out;
if(fileheader(ln, "+++ ", newp) == -1)
goto comment;
free(ln);
if((ln = readline(f, &lnum)) == nil)
goto out;
hunk:
oldcnt = 0;
newcnt = 0;
if(hunkheader(&h, ln, old, new, lnum) == -1)
goto comment;
free(ln);
while(1){
if((ln = readline(f, &lnum)) == nil){
if(oldcnt != h.oldcnt || newcnt != h.newcnt)
sysfatal("%s:%d: malformed hunk", name, lnum);
addhunk(p, &h);
break;
}
switch(ln[0]){
default:
sysfatal("%s:%d: malformed hunk2", name, lnum);
goto out;
case '-':
addold(&h, ln);
oldcnt++;
break;
case '+':
addnew(&h, ln);
newcnt++;
break;
case ' ':
addold(&h, ln);
addnew(&h, ln);
oldcnt++;
newcnt++;
break;
}
free(ln);
if(oldcnt > h.oldcnt || newcnt > h.newcnt)
sysfatal("%s:%d: malformed hunk", name, lnum);
if(oldcnt < h.oldcnt || newcnt < h.newcnt)
continue;
addhunk(p, &h);
if((ln = readline(f, &lnum)) == nil)
goto out;
if(strncmp(ln, "--- ", 4) == 0)
goto patch;
if(strncmp(ln, "@@ ", 3) == 0)
goto hunk;
goto comment;
}
out:
qsort(p->hunk, p->nhunk, sizeof(Hunk), hunkcmp);
free(old);
free(new);
free(ln);
return p;
}
int
rename(int fd, char *name)
{
Dir st;
char *p;
nulldir(&st);
if((p = strrchr(name, '/')) == nil)
st.name = name;
else
st.name = p + 1;
return dirfwstat(fd, &st);
}
int
mkpath(char *path)
{
char *p, buf[ERRMAX];
int f;
if(*path == '\0')
return 0;
for(p = strchr(path+1, '/'); p != nil; p = strchr(p+1, '/')){
*p = '\0';
if(access(path, AEXIST) != 0){
if((f = create(path, OREAD, DMDIR | 0777)) == -1){
rerrstr(buf, sizeof(buf));
if(strstr(buf, "exist") == nil)
return -1;
}
close(f);
}
*p = '/';
}
return 0;
}
void
blat(char *old, char *new, char *o, usize len)
{
char *tmp;
int fd;
if(strcmp(new, "/dev/null") == 0){
if(len != 0)
sysfatal("diff modifies removed file");
if(remove(old) == -1)
sysfatal("removeold %s: %r", old);
return;
}
if(mkpath(new) == -1)
sysfatal("mkpath %s: %r", new);
if((tmp = smprint("%s.tmp%d", new, getpid())) == nil)
sysfatal("smprint: %r");
if((fd = create(tmp, OWRITE, 0666)) == -1)
sysfatal("open %s: %r", tmp);
if(write(fd, o, len) != len)
sysfatal("write %s: %r", tmp);
if(strcmp(old, new) == 0 && remove(old) == -1)
sysfatal("remove %s: %r", old);
if(rename(fd, new) == -1)
sysfatal("create %s: %r", new);
if(close(fd) == -1)
sysfatal("close %s: %r", tmp);
free(tmp);
}
int
slurp(Fbuf *f, char *path)
{
int n, i, fd, sz, len, nlines, linesz;
char *buf;
int *lines;
if((fd = open(path, OREAD)) == -1)
sysfatal("open %s: %r", path);
sz = 8192;
len = 0;
buf = emalloc(sz);
while(1){
if(len == sz){
sz *= 2;
buf = erealloc(buf, sz);
}
n = read(fd, buf + len, sz - len);
if(n == 0)
break;
if(n == -1)
sysfatal("read %s: %r", path);
len += n;
}
nlines = 0;
linesz = 32;
lines = emalloc(linesz*sizeof(int));
lines[nlines++] = 0;
for(i = 0; i < len; i++){
if(buf[i] != '\n')
continue;
if(nlines+1 == linesz){
linesz *= 2;
lines = erealloc(lines, linesz*sizeof(int));
}
lines[nlines++] = i+1;
}
f->len = len;
f->buf = buf;
f->lines = lines;
f->nlines = nlines;
f->lastln = -1;
return 0;
}
char*
search(Fbuf *f, Hunk *h, char *fname)
{
int ln, len, off, fuzz, nfuzz, scanning;
scanning = 1;
len = h->oldlen;
nfuzz = (f->nlines < 250) ? f->nlines : 250;
for(fuzz = 0; scanning && fuzz <= nfuzz; fuzz++){
scanning = 0;
ln = h->oldln - fuzz;
if(ln > f->lastln){
off = f->lines[ln];
if(off + len > f->len)
continue;
scanning = 1;
if(memcmp(f->buf + off, h->old, h->oldlen) == 0){
f->lastln = ln;
return f->buf + off;
}
}
ln = h->oldln + fuzz - 1;
if(ln <= f->nlines){
off = f->lines[ln];
if(off + len >= f->len)
continue;
scanning = 1;
if(memcmp(f->buf + off, h->old, h->oldlen) == 0){
f->lastln = ln;
return f->buf + off;
}
}
}
sysfatal("%s:%d: unable to find hunk offset in %s", fname, h->lnum, h->oldpath);
return nil;
}
char*
append(char *o, int *sz, char *s, char *e)
{
int n;
n = (e - s);
o = erealloc(o, *sz + n);
memcpy(o + *sz, s, n);
*sz += n;
return o;
}
int
apply(Patch *p, char *fname)
{
char *o, *s, *e, *curfile;
int i, osz;
Hunk *h;
Fbuf f;
e = nil;
o = nil;
osz = 0;
curfile = nil;
for(i = 0; i < p->nhunk; i++){
h = &p->hunk[i];
if(curfile == nil || strcmp(curfile, h->newpath) != 0){
if(slurp(&f, h->oldpath) == -1)
sysfatal("slurp %s: %r", h->oldpath);
curfile = h->newpath;
e = f.buf;
}
s = e;
e = search(&f, h, fname);
o = append(o, &osz, s, e);
o = append(o, &osz, h->new, h->new + h->newlen);
e += h->oldlen;
if(i+1 == p->nhunk || strcmp(curfile, p->hunk[i+1].newpath) != 0){
o = append(o, &osz, e, f.buf + f.len);
blat(h->oldpath, h->newpath, o, osz);
if(strcmp(h->newpath, "/dev/null") == 0)
print("%s\n", h->oldpath);
else
print("%s\n", h->newpath);
osz = 0;
}
}
free(o);
return 0;
}
void
freepatch(Patch *p)
{
Hunk *h;
int i;
for(i = 0; i < p->nhunk; i++){
h = &p->hunk[i];
free(h->oldpath);
free(h->newpath);
free(h->old);
free(h->new);
}
free(p->hunk);
free(p->name);
free(p);
}
void
usage(void)
{
fprint(2, "usage: %s [-R] [-p nstrip] [patch...]\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
Biobuf *f;
Patch *p;
int i;
ARGBEGIN{
case 'p':
strip = atoi(EARGF(usage()));
break;
case 'R':
reverse++;
break;
default:
usage();
break;
}ARGEND;
if(reverse){
addnew = addoldfn;
addold = addnewfn;
}else{
addnew = addnewfn;
addold = addoldfn;
}
if(argc == 0){
if((f = Bfdopen(0, OREAD)) == nil)
sysfatal("open stdin: %r");
if((p = parse(f, "stdin")) == nil)
sysfatal("parse patch: %r");
if(apply(p, "stdin") == -1)
sysfatal("apply stdin: %r");
freepatch(p);
Bterm(f);
}else{
for(i = 0; i < argc; i++){
if((f = Bopen(argv[i], OREAD)) == nil)
sysfatal("open %s: %r", argv[i]);
if((p = parse(f, argv[i])) == nil)
sysfatal("parse patch: %r");
if(apply(p, argv[i]) == -1)
sysfatal("apply %s: %r", argv[i]);
freepatch(p);
Bterm(f);
}
}
exits(nil);
}