From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-0.0 required=5.0 tests=T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.4 Received: (qmail 27728 invoked from network); 28 May 2022 21:11:42 -0000 Received: from 9front.inri.net (168.235.81.73) by inbox.vuxu.org with ESMTPUTF8; 28 May 2022 21:11:42 -0000 Received: from mimir.eigenstate.org ([206.124.132.107]) by 9front; Sat May 28 17:10:00 -0400 2022 Received: from stockyard (ue.tmodns.net [172.58.230.182]) by mimir.eigenstate.org (OpenSMTPD) with ESMTPSA id be24853b (TLSv1.2:ECDHE-RSA-AES256-SHA:256:NO) for <9front@9front.org>; Sat, 28 May 2022 14:09:48 -0700 (PDT) Message-ID: <807E093991E805B77323A2A290D6E41A@eigenstate.org> To: 9front@9front.org Date: Sat, 28 May 2022 17:09:46 -0400 From: ori@eigenstate.org In-Reply-To: <8E078F2DFC40157E093D358ACCEB131E@eigenstate.org> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="upas-rumelaaeylgnwjmeijbmvsosbg" List-ID: <9front.9front.org> List-Help: X-Glyph: ➈ X-Bullshit: virtualized extensible element interface Subject: Re: [9front] patch: import replacement for ape/patch Reply-To: 9front@9front.org Precedence: bulk This is a multi-part message in MIME format. --upas-rumelaaeylgnwjmeijbmvsosbg Content-Disposition: inline Content-Type: text/plain; charset="US-ASCII" Content-Transfer-Encoding: 7bit Quoth ori@eigenstate.org: > Quoth ori@eigenstate.org: > > new revision: > > spoke too soon; I broke multiple hunks. > New revision. Tests, which can be applied to the regress suite, are attached. diff b75e549126641108880a24a4ff0b38171eb1a856 uncommitted --- /tmp/diff100000056523 +++ b//sys/man/1/patch @@ -1,0 +1,61 @@ +.TH PATCH 1 +.SH NAME +patch \- apply diffs +.SH SYNOPSIS +.B patch +[ +.B -lR +] +[ +.B -p +.I nstrip +] +[ +.B -f +.I maxfuzz +] +[ +.I patch ... +] +.SH DESCRIPTION +.I Patch +takes a patch file in unified diff format and applies the differences to the file tree. +If the file is not an exact match, it will search forwards and +backwards for the surrounding context up to +.I maxfuzz +lines. +.PP +The following options are supported: +.TP +.B -l +List files affected by the applied patches, one file per line. +.TP +.B -R +Reverse direction of the patch. Additions become removals, +and the new and old file names are swapped. +.TP +.BI -p \ nstrip +Remove the prefix containing +.I nstrip +leading slashes from each file path in the diff file. +.TP +.BI -f \ maxfuzz +Controls how far +.I patch +searches for context when applying a patch. +If not specified, this defaults to 250 lines. +.SH SEE ALSO +.IR diff (1), +.IR git/export (1) +.SH BUGS +.PP +The output of +.B diff -c +is not handled. +.PP +Reject files and backups are not supported. +.PP +All files are processed in memory, which limits +the files handled to 2 gigabytes on 32-bit systems. + + --- /tmp/diff100000056526 +++ b//sys/src/cmd/patch.c @@ -1,0 +1,621 @@ +#include +#include +#include +#include + +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; +int maxfuzz = 250; +int listpatch; +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); + if(*e != ',') + return -1; + 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 || *e != ',') + return -1; + 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, *oldhdr, *newhdr, *hunkhdr, **oldp, **newp; + int inbody, oldcnt, newcnt, midhunk, lnum; + Patch *p; + Hunk h, hh; + + ln = nil; + lnum = 0; + inbody = 0; + midhunk = 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); + } + sysfatal("%s: could not find start of patch", name); + +patch: + oldhdr = ln; + ln = nil; + newhdr = nil; + hunkhdr = nil; + if(fileheader(oldhdr, "--- ", oldp) == -1){ + if(!inbody) + goto comment; + else if(midhunk) + goto mishunk; + else + goto out; + } + if((newhdr = readline(f, &lnum)) == nil) + goto out; + if(fileheader(newhdr, "+++ ", newp) == -1){ + if(!inbody) + goto comment; + else if(midhunk) + goto mishunk; + else + goto out; + } + if((hunkhdr = readline(f, &lnum)) == nil) + goto out; +hunk: + if(hunkheader(&hh, hunkhdr, old, new, lnum) == -1){ + if(!inbody) + goto comment; + else if(!midhunk) + goto out; +mishunk: + if(!addmiss(&h, oldhdr, &oldcnt, &newcnt)) + goto out; + if(!addmiss(&h, newhdr, &oldcnt, &newcnt)) + goto out; + if(!addmiss(&h, hunkhdr, &oldcnt, &newcnt)) + goto out; + goto nextln; + } + if(midhunk) + addhunk(p, &h); + h = hh; + inbody = 1; + oldcnt = 0; + newcnt = 0; + midhunk = 0; + free(ln); + free(oldhdr); + free(newhdr); + free(hunkhdr); + oldhdr = nil; + newhdr = nil; + while(1){ +nextln: + 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: + if(oldcnt != h.oldcnt || newcnt != h.newcnt) + sysfatal("%s:%d: malformed hunk", name, lnum); + addhunk(p, &h); + goto out; + case '@': + midhunk = 1; + hunkhdr = ln; + ln = nil; + goto hunk; + case '-': + if(strncmp(ln, "--- ", 4) == 0){ + midhunk = 1; + goto patch; + } + addold(&h, ln); + oldcnt++; + break; + case '+': + addnew(&h, ln); + newcnt++; + break; + case ' ': + addold(&h, ln); + addnew(&h, ln); + oldcnt++; + newcnt++; + break; + } + } + +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); +} + +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((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(strcmp(new, old) == 0) + remove(new); + 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 < maxfuzz) ? f->nlines : maxfuzz; + 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(listpatch) + 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] [-f maxfuzz] [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; + case 'f': + maxfuzz = atoi(EARGF(usage())); + break; + case 'l': + listpatch++; + 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); +} --upas-rumelaaeylgnwjmeijbmvsosbg Content-Disposition: attachment; filename=regress.patch Content-Type: text/plain; charset="US-ASCII" Content-Transfer-Encoding: 7bit diff f9f95b10dfcda3981a820a24e7929af6afbebad0 uncommitted --- /tmp/diff100000056765 +++ b/cmd/patch/basic.in @@ -1,0 +1,93 @@ +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +13 +12 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 --- /tmp/diff100000056768 +++ b/cmd/patch/create.expected @@ -1,0 +1,12 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 --- /tmp/diff100000056771 +++ b/cmd/patch/create.patch @@ -1,0 +1,15 @@ +--- /dev/null ++++ create.out +@@ -1,0 +1,12 @@ ++1 ++2 ++3 ++4 ++5 ++6 ++7 ++8 ++9 ++10 ++11 ++12 --- /tmp/diff100000056774 +++ b/cmd/patch/header.expected @@ -1,0 +1,100 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 --- /tmp/diff100000056777 +++ b/cmd/patch/header.in @@ -1,0 +1,93 @@ +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +13 +12 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 --- /tmp/diff100000056780 +++ b/cmd/patch/header.patch @@ -1,0 +1,53 @@ +diff should handle headers just fine, so +this file contains a few lines of header +and footer, with a couple of false starts, +consisting of lines starting with +--- some words +but no immediately following ++++ words +lines + +--- header.in ++++ header.out +@@ -1,3 +1,5 @@ ++1 ++2 + 3 + 4 + 5 +@@ -10,8 +12,8 @@ + 12 + 13 + 14 +-13 +-12 ++15 ++16 + 17 + 18 + 19 +@@ -35,6 +37,8 @@ + 37 + 38 + 39 ++40 ++41 + 42 + 43 + 44 +@@ -80,6 +84,7 @@ + 84 + 85 + 86 ++87 + 88 + 89 + 90 +@@ -91,3 +96,5 @@ + 96 + 97 + 98 ++99 ++100 +and here is the footer that should +also be ignored. --- /tmp/diff100000056783 +++ b/cmd/patch/mkfile @@ -1,0 +1,5 @@ +[2=1] echo fail: $1 $2 + >[2=1] diff -u $1 $2 + exit mismatch + } + status=() +} + +fn checkpatch{ + rm -f $1.out + patch $1.patch + check $1.out $1.expected +} + +checkpatch basic +checkpatch header +checkpatch create + +seq 12 > delete.out +patch delete.patch +test ! -f delete.out + +rm -f multifile^(1 2)^.out +patch multifile.patch +check multifile1.out multifile1.expected +check multifile2.out multifile2.expected + +status=() --upas-rumelaaeylgnwjmeijbmvsosbg--