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:
parent
761bf6c347
commit
07e1620911
1 changed files with 611 additions and 0 deletions
611
sys/src/cmd/patch.c
Normal file
611
sys/src/cmd/patch.c
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue