9front - general discussion about 9front
 help / color / mirror / Atom feed
* [9front] patch: import replacement for ape/patch
@ 2022-05-22 22:57 ori
  2022-05-22 23:09 ` ori
  0 siblings, 1 reply; 12+ messages in thread
From: ori @ 2022-05-22 22:57 UTC (permalink / raw)
  To: 9front

We've been dragging along an antique version of Gnu patch
as part of ape. It's a bulky program with built in remote
code execution as part of ed-style diffs.

I'd like to eventually kill it. Let's start
by adding a replacement:

diff 6fbb1acc8fa0b6655b14e8c46240a4a8d2d8c672 uncommitted
--- /dev/null
+++ b//sys/man/1/patch
@@ -1,0 +1,57 @@
+.TH PATCH 1
+.SH NAME
+patch \- apply diffs
+.SH SYNOPSIS
+.B patch
+[
+.B -R
+]
+[
+.B -p
+.I nstrip
+]
+[
+.B -f
+.I maxfuzz
+]
+[
+.B patch...
+]
+.SH DESCRIPTION
+.I Patch
+will take a patch file in unified diff format, and apply it,
+skipping leading and trailing junk.
+If the file is not an exact match, it will search forwards and
+backwards for the surrounding context up to
+.I maxfuzz
+lines.
+
+The following options are supported:
+.TP
+.I -R
+Reverse direction of the patch. Addtions become removals,
+and the new and old file names are swapped.
+.TP
+.I -p nstrip
+Remove
+.I nstrip
+elements from the paths to the files.
+.TP
+.I -f maxfuzz
+Controls how far
+.I patch
+searches for context when applying a patch.
+.SH SEE ALSO
+.IR diff (1)
+.IR git/export (1)
+
+.SH BUGS
+.PP
+The output of
+.IR 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.


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-22 22:57 [9front] patch: import replacement for ape/patch ori
@ 2022-05-22 23:09 ` ori
  2022-05-23  3:18   ` Amavect
  0 siblings, 1 reply; 12+ messages in thread
From: ori @ 2022-05-22 23:09 UTC (permalink / raw)
  To: 9front

whoops -- forgot to attach the acutal code. Attached,
with some fixups for the manpage.

diff 6fbb1acc8fa0b6655b14e8c46240a4a8d2d8c672 uncommitted
--- /dev/null
+++ b//sys/man/1/patch
@@ -1,0 +1,58 @@
+.TH PATCH 1
+.SH NAME
+patch \- apply diffs
+.SH SYNOPSIS
+.B patch
+[
+.B -R
+]
+[
+.B -p
+.I nstrip
+]
+[
+.B -f
+.I maxfuzz
+]
+[
+.B patch...
+]
+.SH DESCRIPTION
+.I Patch
+will take a patch file in unified diff format, and apply it,
+skipping leading and trailing junk.
+If the file is not an exact match, it will search forwards and
+backwards for the surrounding context up to
+.I maxfuzz
+lines.
+
+The following options are supported:
+.TP
+.I -R
+Reverse direction of the patch. Addtions become removals,
+and the new and old file names are swapped.
+.TP
+.I -p nstrip
+Remove
+.I nstrip
+elements from the paths to the files.
+.TP
+.I -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
+.IR 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.
--- /dev/null
+++ b//sys/src/cmd/patch.c
@@ -1,0 +1,548 @@
+#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;
+int	maxfuzz	= 256;
+
+char*
+readline(Biobuf *f, char *name, int *lnum)
+{
+	char *ln;
+
+	if((ln = Brdstr(f, '\n', 0)) == nil)
+		sysfatal("read %s:%d: %r", name, *lnum);
+	*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)
+{
+	char *p, *e;
+	int len, n;
+
+	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;
+	n = strip;
+	for(p = s; *p && n > 0; p++){
+		if(*p != '/')
+			continue;
+		s = p + 1;
+		n--;
+	}
+	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
+addnew(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
+addold(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;
+}
+
+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*)b)->oldln - ((Hunk*)a)->oldln;
+}
+
+Patch*
+parse(Biobuf *f, char *name)
+{
+	char *ln, *old, *new, **oldp, **newp;
+	int inbody, oldcnt, newcnt, lnum;
+	Patch *p;
+	Hunk h;
+
+	ln = nil;
+	lnum = 0;
+	inbody = 0;
+	p = emalloc(sizeof(Patch));
+	if(!reverse){
+		oldp = &old;
+		newp = &new;
+	}else{
+		oldp = &new;
+		newp = &old;
+	}
+comment:
+	free(ln);
+	while((ln = readline(f, name, &lnum)) != nil){
+		if(strncmp(ln, "--- ", 4) == 0)
+			goto patch;
+		free(ln);
+	}
+	sysfatal("%s: could not find start of patch", name);
+
+patch:
+	if(fileheader(ln, "--- ", oldp) == -1){
+		if(inbody)
+			goto out;
+		else
+			goto comment;
+	}
+	free(ln);
+	ln = readline(f, name, &lnum);
+	if(fileheader(ln, "+++ ", newp) == -1){
+		if(inbody)
+			goto out;
+		else
+			goto comment;
+	
+	}
+	free(ln);
+	ln = readline(f, name, &lnum);
+hunk:
+	if(hunkheader(&h, ln, old, new, lnum) == -1)
+		goto comment;
+	free(ln);
+
+	inbody = 1;
+	oldcnt = 0;
+	newcnt = 0;
+	while(1){
+		if((ln = readline(f, name, &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 patch;
+		case '@':
+			addhunk(p, &h);
+			goto hunk;
+		case '-':
+			if(reverse)
+				addnew(&h, ln);
+			else
+				addold(&h, ln);
+			oldcnt++;
+			break;
+		case '+':
+			if(reverse)
+				addold(&h, ln);
+			else
+				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;
+
+	nulldir(&st);
+	st.name = name;
+	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("remove %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, "/dev/null") != 0 && remove(old) == -1)
+		sysfatal("remove %s: %r", old);
+	if(strcmp(new, old) != 0 && remove(new) == -1)
+		sysfatal("remove %s: %r", new);
+	if(rename(fd, new) == -1)
+		sysfatal("rename %s => %s: %r", tmp, 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 = 0;
+	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 patch offset", fname, h->lnum);
+	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->oldpath) != 0){
+			if(slurp(&f, h->oldpath) == -1)
+				sysfatal("slurp %s: %r", h->oldpath);
+			curfile = h->oldpath;
+			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].oldpath) != 0){
+			o = append(o, &osz, e, f.buf + f.len);
+			blat(h->oldpath, h->newpath, o, osz);
+			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 = 1;
+		break;
+	case 'f':
+		maxfuzz = atoi(EARGF(usage()));
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND;
+
+	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);
+}


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-22 23:09 ` ori
@ 2022-05-23  3:18   ` Amavect
  2022-05-23 12:57     ` Humm
  2022-05-23 21:40     ` ori
  0 siblings, 2 replies; 12+ messages in thread
From: Amavect @ 2022-05-23  3:18 UTC (permalink / raw)
  To: 9front

[-- Attachment #1: Type: text/plain, Size: 889 bytes --]

On Sun, 22 May 2022 19:09:40 -0400
ori@eigenstate.org wrote:

> We've been dragging along an antique version of Gnu patch
> as part of ape. It's a bulky program with built in remote
> code execution as part of ed-style diffs.

This will conflict with /rc/bin/patch and /sys/man/1/patch,
which I'm all for removing.

Attached is my edit of the man page:
correct Synopsis patch italics
clarify what patch does
Addtions -> Additions
fix table italics
clarify -p
in See Also, add comma
in Bugs, use all bold for commands

I can't seem to apply your diff with your program:
cpu% touch patch patch.c
cpu% lc
6.out*	diff	patch	patch.c
cpu% 6.out diff
6.out: diff:4: unable to find patch offset
cpu% 6.out -p4 diff
6.out: open null: 'null' not found
cpu% ape/patch -i diff
patching file `patch'
patching file `patch.c'
cpu% 

I'm guessing /dev/null is special-cased in gnu patch.

Thanks,
Amavect

[-- Attachment #2: patch.man --]
[-- Type: application/x-troff-man, Size: 967 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-23  3:18   ` Amavect
@ 2022-05-23 12:57     ` Humm
  2022-05-24  2:38       ` Amavect
  2022-05-23 21:40     ` ori
  1 sibling, 1 reply; 12+ messages in thread
From: Humm @ 2022-05-23 12:57 UTC (permalink / raw)
  To: 9front

>Content-Type: application/x-troff-man

text/troff exists.

	lines.

	The following options are supported:
	.\" …
	.IR git/export (1)

	.SH BUGS

In troff, blank lines are UB.  They should be removed or replaced by 
lines with just a period.

-- 
Humm

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-23  3:18   ` Amavect
  2022-05-23 12:57     ` Humm
@ 2022-05-23 21:40     ` ori
  2022-05-23 23:51       ` ori
  1 sibling, 1 reply; 12+ messages in thread
From: ori @ 2022-05-23 21:40 UTC (permalink / raw)
  To: 9front

Quoth Amavect <amavect@gmail.com>:
> On Sun, 22 May 2022 19:09:40 -0400
> ori@eigenstate.org wrote:
> 
> > We've been dragging along an antique version of Gnu patch
> > as part of ape. It's a bulky program with built in remote
> > code execution as part of ed-style diffs.
> 
> This will conflict with /rc/bin/patch and /sys/man/1/patch,
> which I'm all for removing.

done -- a59e61a6a4e11e0256da0d209afa38ccacd460a2

> Attached is my edit of the man page:
> correct Synopsis patch italics
> clarify what patch does
> Addtions -> Additions
> fix table italics
> clarify -p
> in See Also, add comma
> in Bugs, use all bold for commands

Taken, thanks.

> I can't seem to apply your diff with your program:
> cpu% touch patch patch.c
> cpu% lc
> 6.out*	diff	patch	patch.c
> cpu% 6.out diff
> 6.out: diff:4: unable to find patch offset
> cpu% 6.out -p4 diff
> 6.out: open null: 'null' not found
> cpu% ape/patch -i diff
> patching file `patch'
> patching file `patch.c'
> cpu% 
> 
> I'm guessing /dev/null is special-cased in gnu patch.
> 

Yeah, and there were a couple of other bugs around that,
like handling paths starting with (or repeating) /.

I also fixed the '--' ambiguity: Hunk headers start
with '---', but if you have a line starting with '--',
then diff -u can generate an output line that also
starts with '---'; to disambiguate, you need to look
ahead two lines for a line starting with '@@'. The code
to handle it is an ugly goto-ridden mess :(

new revision:

diff 6fbb1acc8fa0b6655b14e8c46240a4a8d2d8c672 uncommitted
--- /dev/null
+++ b//sys/man/1/patch
@@ -1,0 +1,58 @@
+.TH PATCH 1
+.SH NAME
+patch \- apply diffs
+.SH SYNOPSIS
+.B patch
+[
+.B -R
+]
+[
+.B -p
+.I nstrip
+]
+[
+.B -f
+.I maxfuzz
+]
+[
+.B patch...
+]
+.SH DESCRIPTION
+.I Patch
+will take a patch file in unified diff format, and apply it,
+skipping leading and trailing junk.
+If the file is not an exact match, it will search forwards and
+backwards for the surrounding context up to
+.I maxfuzz
+lines.
+
+The following options are supported:
+.TP
+.I -R
+Reverse direction of the patch. Addtions become removals,
+and the new and old file names are swapped.
+.TP
+.I -p nstrip
+Remove
+.I nstrip
+elements from the paths to the files.
+.TP
+.I -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
+.IR 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.
--- /dev/null
+++ b//sys/src/cmd/patch.c
@@ -1,0 +1,617 @@
+#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;
+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*)b)->oldln - ((Hunk*)a)->oldln;
+}
+
+Patch*
+parse(Biobuf *f, char *name)
+{
+	char *ln, *old, *new, *oldhdr, *newhdr, *hunkhdr, **oldp, **newp;
+	int inbody, oldcnt, newcnt, fromdash, lnum;
+	Patch *p;
+	Hunk h, hh;
+
+	ln = nil;
+	lnum = 0;
+	inbody = 0;
+	fromdash = 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(fromdash)
+			goto mishunk;
+		else
+			goto out;
+	}
+	if((newhdr = readline(f, &lnum)) == nil)
+		goto out;
+	if(fileheader(newhdr, "+++ ", newp) == -1){
+		if(!inbody)
+			goto comment;
+		else if(fromdash)
+			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(!fromdash)
+			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(inbody)
+		addhunk(p, &h);
+	h = hh;
+	inbody = 1;
+	oldcnt = 0;
+	newcnt = 0;
+	fromdash = 0;
+	free(ln);
+	free(oldhdr);
+	free(newhdr);
+	free(hunkhdr);
+	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 patch;
+		case '@':
+			addhunk(p, &h);
+			goto hunk;
+		case '-':
+			if(strncmp(ln, "--- ", 4) == 0){
+				fromdash = 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, "/dev/null") != 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 = 0;
+	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);
+}


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-23 21:40     ` ori
@ 2022-05-23 23:51       ` ori
  2022-05-28 21:09         ` ori
  0 siblings, 1 reply; 12+ messages in thread
From: ori @ 2022-05-23 23:51 UTC (permalink / raw)
  To: 9front

Quoth ori@eigenstate.org:
> new revision:

spoke too soon; I broke multiple hunks.


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-23 12:57     ` Humm
@ 2022-05-24  2:38       ` Amavect
  2022-05-24 16:39         ` Humm
  0 siblings, 1 reply; 12+ messages in thread
From: Amavect @ 2022-05-24  2:38 UTC (permalink / raw)
  To: 9front

[-- Attachment #1: Type: text/plain, Size: 1078 bytes --]

On Mon, 23 May 2022 12:57:23 +0000
Humm <hummsmith42@gmail.com> wrote:

> >Content-Type: application/x-troff-man  
> 
> text/troff exists.

A man page is a specific kind of troff file.
(do mime types even matter?)

> 
> 	The following options are supported:
> 	.\" …
> 	.IR git/export (1)
> 
> 	.SH BUGS
> 
> In troff, blank lines are UB.  They should be removed or replaced by 
> lines with just a period.
> 

Is that true? I'm having trouble finding a reference.

https://www.gnu.org/software/groff/manual/html_node/Basics.html
Sometimes a new output line should be started even though the current
line is not yet full; for example, at the end of a paragraph. To do
this it is possible to cause a break, which starts a new output line.
Some requests cause a break automatically, as normally do blank input
lines and input lines beginning with a space.

That blank line needs to be removed anyways since it adds extra space
between the SEE ALSO section and BUGS section.
Also fixed a typo: apply -> applies
See attached.

Thanks,
Amavect

[-- Attachment #2: patch.man --]
[-- Type: application/x-troff-man, Size: 1023 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-24  2:38       ` Amavect
@ 2022-05-24 16:39         ` Humm
  2022-05-24 18:44           ` umbraticus
                             ` (2 more replies)
  0 siblings, 3 replies; 12+ messages in thread
From: Humm @ 2022-05-24 16:39 UTC (permalink / raw)
  To: 9front

Quoth Amavect:
>On Mon, 23 May 2022 12:57:23 +0000
>Humm <hummsmith42@gmail.com> wrote:
>
>> >Content-Type: application/x-troff-man
>>
>> text/troff exists.
>
>A man page is a specific kind of troff file.

Quoting RFC4263:

>Optional parameters:
>[…]
>  process: Human-readable additional information for formatting,
>     including environment variables, preprocessor arguments and
>     order, formatter arguments, and postprocessors.  The parameter
>     value may need to be quoted or encoded as provided for by
>     [N4.RFC2045] as amended by [N5.RFC2231] and [N6.Errata].
>     Generating implementations must not encode executable content
>     and other implementations must not attempt any execution or
>     other interpretation of the parameter value, as the parameter
>     value may be prose text.  Implementations SHOULD present the
>     parameter (after reassembly of continuation parameters, etc.)
>     as information related to the media type, particularly if the
>     media content is not immediately available (e.g., as with
>     message/external-body composite media [N3.RFC2046]).

As examples are provided:

	text/troff ; process="dformat | pic -n | troff -ms"

and

	text/troff ; process="use pic -n then troff -ms"

>(do mime types even matter?)

Yes.  Well-behaved MUAs decide what to do with stuff by looking at 
those.

For troff: No.

>> In troff, blank lines are UB.  They should be removed or replaced by
>> lines with just a period.
>>
>
>Is that true? I'm having trouble finding a reference.

While much documentation for specific troffs indeed says that blank 
lines have the same effect as `.sp 1`—they do implement that, after 
all—that behavior is not ubiquitous.  A warning is given, for example, 
by mandoc.  Quoting mandoc(1):

>blank line in fill mode, using .sp
>  (mdoc) The meaning of blank input lines is only well-defined in 
>  non-fill mode: In fill mode, line breaks of text input lines are not 
>  supposed to be significant.  However, for compatibility with groff, 
>  blank lines in fill mode are formatted like sp requests.  To request 
>  a paragraph break, use Pp instead of a blank line.

>https://www.gnu.org/software/groff/manual/html_node/Basics.html
>Sometimes a new output line should be started even though the current
>line is not yet full; for example, at the end of a paragraph. To do
>this it is possible to cause a break, which starts a new output line.
>Some requests cause a break automatically, as normally do blank input
>lines and input lines beginning with a space.

The groff docs are obnoxious in not telling you what is extension and 
what not or how portable something is.

-- 
Humm

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-24 16:39         ` Humm
@ 2022-05-24 18:44           ` umbraticus
  2022-05-24 19:42           ` Lyndon Nerenberg (VE7TFX/VE6BBM)
  2022-05-24 21:19           ` Amavect
  2 siblings, 0 replies; 12+ messages in thread
From: umbraticus @ 2022-05-24 18:44 UTC (permalink / raw)
  To: 9front

/sys/doc/troff.ms:/Blank

umbraticus

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-24 16:39         ` Humm
  2022-05-24 18:44           ` umbraticus
@ 2022-05-24 19:42           ` Lyndon Nerenberg (VE7TFX/VE6BBM)
  2022-05-24 21:19           ` Amavect
  2 siblings, 0 replies; 12+ messages in thread
From: Lyndon Nerenberg (VE7TFX/VE6BBM) @ 2022-05-24 19:42 UTC (permalink / raw)
  To: 9front

Humm writes:

> by mandoc.  Quoting mandoc(1):

Ignore mandoc.  It's just a piss-poor partial reimplementation
of nroff.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-24 16:39         ` Humm
  2022-05-24 18:44           ` umbraticus
  2022-05-24 19:42           ` Lyndon Nerenberg (VE7TFX/VE6BBM)
@ 2022-05-24 21:19           ` Amavect
  2 siblings, 0 replies; 12+ messages in thread
From: Amavect @ 2022-05-24 21:19 UTC (permalink / raw)
  To: 9front

[-- Attachment #1: Type: text/plain, Size: 772 bytes --]

On Tue, 24 May 2022 16:39:28 +0000
Humm <hummsmith42@gmail.com> wrote:

> Quoting RFC4263:

Neat, but I have no clue how to configure process= in claws mail.
Since application/x-troff-man is nonstandard,
I'll just default it to text/troff and call it a day.

> While much documentation for specific troffs indeed says that blank 
> lines have the same effect as `.sp 1`—they do implement that, after 
> all—that behavior is not ubiquitous.  A warning is given, for
> example, by mandoc.  Quoting mandoc(1):

There's disagreement from others, but we can all agree that .PP is the
correct choice for new paragraphs.
It has slightly shorter spacing than a blank line in page.
Attached is the final revision for ori's patch man page.

Thanks,
Amavect

[-- Attachment #2.1: Type: text/plain, Size: 306 bytes --]

from postmaster@9front:
The following attachment had content that we can't
prove to be harmless.  To avoid possible automatic
execution, we changed the content headers.
The original header was:

	Content-Type: text/troff
	Content-Transfer-Encoding: 7bit
	Content-Disposition: attachment; filename=patch.man

[-- Attachment #2.2: patch.man.suspect --]
[-- Type: application/octet-stream, Size: 1027 bytes --]

.TH PATCH 1
.SH NAME
patch \- apply diffs
.SH SYNOPSIS
.B patch
[
.B -R
]
[
.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 -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.


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [9front] patch: import replacement for ape/patch
  2022-05-23 23:51       ` ori
@ 2022-05-28 21:09         ` ori
  0 siblings, 0 replies; 12+ messages in thread
From: ori @ 2022-05-28 21:09 UTC (permalink / raw)
  To: 9front

[-- Attachment #1: Type: text/plain, Size: 12793 bytes --]

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 <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;
+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);
+}

[-- Attachment #2: regress.patch --]
[-- Type: text/plain, Size: 4921 bytes --]

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 @@
+</$objtype/mkfile
+
+TEST=patch
+
+<../../regress
--- /tmp/diff100000056786
+++ b/cmd/patch/multifile.patch
@@ -1,0 +1,86 @@
+--- multifile2.in
++++ multifile2.out
+@@ -1,21 +1,19 @@
+-77777
+ 77778
+ 77779
+ 77780
+ 77781
+ 77782
+-77783
+-77784
+-77785
+-77786
+-77787
+-77788
+ 77789
++77788
++77787
++77786
++77785
++77784
++77783
+ 77790
+ 77791
+ 77792
+ 77793
+ 77794
+-77795
+ 77796
+ 77797
+--- multifile1.in
++++ multifile1.out
+@@ -14,16 +14,6 @@
+ 12
+ 17
+ 18
+-19
+-20
+-21
+-22
+-23
+-24
+-25
+-26
+-27
+-28
+ 29
+ 30
+ 31
+@@ -32,20 +22,6 @@
+ 34
+ 35
+ 36
+-37
+-38
+-39
+-42
+-43
+-44
+-45
+-46
+-47
+-48
+-49
+-50
+-51
+-52
+ 53
+ 54
+ 55
+@@ -73,11 +49,11 @@
+ 77
+ 78
+ 79
+-80
+-81
+-82
+-83
+ 84
++83
++82
++81
++80
+ 85
+ 86
+ 88
--- /tmp/diff100000056789
+++ b/cmd/patch/multifile1.expected
@@ -1,0 +1,69 @@
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+13
+12
+17
+18
+29
+30
+31
+32
+33
+34
+35
+36
+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
+84
+83
+82
+81
+80
+85
+86
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
--- /tmp/diff100000056792
+++ b/cmd/patch/multifile1.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/diff100000056795
+++ b/cmd/patch/multifile2.expected
@@ -1,0 +1,19 @@
+77778
+77779
+77780
+77781
+77782
+77789
+77788
+77787
+77786
+77785
+77784
+77783
+77790
+77791
+77792
+77793
+77794
+77796
+77797
--- /tmp/diff100000056798
+++ b/cmd/patch/multifile2.in
@@ -1,0 +1,21 @@
+77777
+77778
+77779
+77780
+77781
+77782
+77783
+77784
+77785
+77786
+77787
+77788
+77789
+77790
+77791
+77792
+77793
+77794
+77795
+77796
+77797
--- /tmp/diff100000056801
+++ b/cmd/patch/patch.rc
@@ -1,0 +1,31 @@
+#!/bin/rc -e
+
+fn check{
+	if(! cmp $1 $2){
+		>[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=()

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2022-05-28 21:11 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-05-22 22:57 [9front] patch: import replacement for ape/patch ori
2022-05-22 23:09 ` ori
2022-05-23  3:18   ` Amavect
2022-05-23 12:57     ` Humm
2022-05-24  2:38       ` Amavect
2022-05-24 16:39         ` Humm
2022-05-24 18:44           ` umbraticus
2022-05-24 19:42           ` Lyndon Nerenberg (VE7TFX/VE6BBM)
2022-05-24 21:19           ` Amavect
2022-05-23 21:40     ` ori
2022-05-23 23:51       ` ori
2022-05-28 21:09         ` ori

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).