From: Omar Polo Subject: got patch: reverse patches To: gameoftrees@openbsd.org Date: Sat, 23 Apr 2022 10:22:21 +0200 got-read-patch currently ignores the "\ No newline at end of file" marker after a "-" line: this is to simplify the matching routines and because to apply a diff we're really interested only in the marker if it follows a "+" line. This, however, gets in the way of reversing patches because then a "\" marker after a "-" line suddely becomes interesting... I intend to commit the following in two steps if it looks good: first the refactoring where got-read-patch sends all the "\" lines, and a second commit for the reversing itself. (I've sent this diff previously, now it's rebased on top of recent changes and not split into two) diff refs/heads/main refs/heads/patchrev2 blob - bd6e06c564ce9499b313b1d667097c58c2d16873 blob + b91cfb2cb4963721a9a86be9c2cdb397bc1a9278 --- got/got.1 +++ got/got.1 @@ -1285,7 +1285,7 @@ option) .El .El .Tg pa -.It Cm patch Oo Fl n Oc Oo Fl p Ar strip-count Oc Op Ar patchfile +.It Cm patch Oo Fl n Oc Oo Fl p Ar strip-count Oc Oo Fl R Oc Op Ar patchfile .Dl Pq alias: Cm pa Apply changes from .Ar patchfile @@ -1367,6 +1367,8 @@ and path prefixes generated by .Xr git-diff 1 will be recognized and stripped automatically. +.It Fl R +Reverse the patch before applying it. .El .Tg rv .It Cm revert Oo Fl p Oc Oo Fl F Ar response-script Oc Oo Fl R Oc Ar path ... blob - f5b90a539a83d07a3ca6b4ca4ea572d797874a00 blob + 80e5d2fbcd031246f1194edf450fa9da6e1a2901 --- got/got.c +++ got/got.c @@ -7153,8 +7153,8 @@ done: __dead static void usage_patch(void) { - fprintf(stderr, "usage: %s patch [-n] [-p strip-count] [patchfile]\n", - getprogname()); + fprintf(stderr, "usage: %s patch [-n] [-p strip-count] " + "[-R] [patchfile]\n", getprogname()); exit(1); } @@ -7243,10 +7243,10 @@ cmd_patch(int argc, char *argv[]) struct got_repository *repo = NULL; const char *errstr; char *cwd = NULL; - int ch, nop = 0, strip = -1; + int ch, nop = 0, strip = -1, reverse = 0; int patchfd; - while ((ch = getopt(argc, argv, "np:")) != -1) { + while ((ch = getopt(argc, argv, "np:R")) != -1) { switch (ch) { case 'n': nop = 1; @@ -7257,6 +7257,9 @@ cmd_patch(int argc, char *argv[]) errx(1, "pathname strip count is %s: %s", errstr, optarg); break; + case 'R': + reverse = 1; + break; default: usage_patch(); /* NOTREACHED */ @@ -7304,7 +7307,7 @@ cmd_patch(int argc, char *argv[]) err(1, "pledge"); #endif - error = got_patch(patchfd, worktree, repo, nop, strip, + error = got_patch(patchfd, worktree, repo, nop, strip, reverse, &patch_progress, NULL, check_cancelled, NULL); done: blob - 62c76b0eb2926a03364ef10e416183cdfa18f398 blob + bc2e95bb4fa535617bc28f76935a78e36dea8a3e --- include/got_patch.h +++ include/got_patch.h @@ -32,4 +32,4 @@ typedef const struct got_error *(*got_patch_progress_c */ const struct got_error * got_patch(int, struct got_worktree *, struct got_repository *, int, int, - got_patch_progress_cb, void *, got_cancel_cb, void *); + int, got_patch_progress_cb, void *, got_cancel_cb, void *); blob - adf9459c88ef262d9070733fe62620364926f5db blob + 9a66825918efbd6b934add0ed0636e397fb31edd --- lib/patch.c +++ lib/patch.c @@ -55,7 +55,8 @@ struct got_patch_hunk { STAILQ_ENTRY(got_patch_hunk) entries; const struct got_error *err; long offset; - int nonl; + int old_nonl; + int new_nonl; long old_from; long old_lines; long new_from; @@ -152,6 +153,7 @@ recv_patch(struct imsgbuf *ibuf, int *done, struct got struct got_imsg_patch patch; struct got_patch_hunk *h = NULL; size_t datalen; + int lastmode = -1; memset(p, 0, sizeof(*p)); STAILQ_INIT(&p->head); @@ -215,10 +217,11 @@ recv_patch(struct imsgbuf *ibuf, int *done, struct got case GOT_IMSG_PATCH_DONE: goto done; case GOT_IMSG_PATCH_HUNK: - if (h != NULL && h->nonl) { + if (h != NULL && (h->old_nonl || h->new_nonl)) { err = got_error(GOT_ERR_PATCH_MALFORMED); goto done; } + lastmode = -1; datalen = imsg.hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(hdr)) { err = got_error(GOT_ERR_PRIVSEP_LEN); @@ -252,14 +255,20 @@ recv_patch(struct imsgbuf *ibuf, int *done, struct got err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } - if (h->nonl) - err = got_error(GOT_ERR_PATCH_MALFORMED); - if (*t == '\\') - h->nonl = 1; - else + + if (*t != '\\') err = pushline(h, t); + else if (lastmode == '-') + h->old_nonl = 1; + else if (lastmode == '+') + h->new_nonl = 1; + else + err = got_error(GOT_ERR_PATCH_MALFORMED); + if (err) goto done; + + lastmode = *t; break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); @@ -399,7 +408,7 @@ done: static const struct got_error * apply_hunk(FILE *tmp, struct got_patch_hunk *h, long *lineno) { - size_t i = 0; + size_t i, new = 0; for (i = 0; i < h->len; ++i) { switch (*h->lines[i]) { @@ -411,9 +420,10 @@ apply_hunk(FILE *tmp, struct got_patch_hunk *h, long * (*lineno)++; break; case '+': + new++; if (fprintf(tmp, "%s", h->lines[i] + 1) < 0) return got_error_from_errno("fprintf"); - if (i != h->len - 1 || !h->nonl) { + if (new != h->new_lines || !h->new_nonl) { if (fprintf(tmp, "\n") < 0) return got_error_from_errno( "fprintf"); @@ -678,10 +688,39 @@ done: return err; } +static void +reverse_patch(struct got_patch *p) +{ + struct got_patch_hunk *h; + size_t i; + long tmp; + + STAILQ_FOREACH(h, &p->head, entries) { + tmp = h->old_from; + h->old_from = h->new_from; + h->new_from = tmp; + + tmp = h->old_lines; + h->old_lines = h->new_lines; + h->new_lines = tmp; + + tmp = h->old_nonl; + h->old_nonl = h->new_nonl; + h->new_nonl = tmp; + + for (i = 0; i < h->len; ++i) { + if (*h->lines[i] == '+') + *h->lines[i] = '-'; + else if (*h->lines[i] == '-') + *h->lines[i] = '+'; + } + } +} + const struct got_error * got_patch(int fd, struct got_worktree *worktree, struct got_repository *repo, - int nop, int strip, got_patch_progress_cb progress_cb, void *progress_arg, - got_cancel_cb cancel_cb, void *cancel_arg) + int nop, int strip, int reverse, got_patch_progress_cb progress_cb, + void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_fileindex *fileindex = NULL; @@ -740,6 +779,9 @@ got_patch(int fd, struct got_worktree *worktree, struc if (err || done) break; + if (reverse) + reverse_patch(&p); + err = got_worktree_patch_check_path(p.old, p.new, &oldpath, &newpath, worktree, repo, fileindex); if (err == NULL) blob - b72f7f88829c3931ef203cba5d632edc4dc06195 blob + 879b77f8142d65729f38b9389c48cf5a78299027 --- libexec/got-read-patch/got-read-patch.c +++ libexec/got-read-patch/got-read-patch.c @@ -288,7 +288,7 @@ send_line(const char *line) } static const struct got_error * -peek_special_line(FILE *fp, int send) +peek_special_line(FILE *fp) { const struct got_error *err; int ch; @@ -299,7 +299,7 @@ peek_special_line(FILE *fp, int send) return NULL; } - if (ch == '\\' && send) { + if (ch == '\\') { err = send_line("\\"); if (err) return err; @@ -396,7 +396,7 @@ parse_hunk(FILE *fp, int *ok) if ((ch == '-' && leftold == 0) || (ch == '+' && leftnew == 0)) { - err = peek_special_line(fp, ch == '+'); + err = peek_special_line(fp); if (err) goto done; } blob - af1fd72e33001d46b8f822e0b0a7f00a5b1ad7b7 blob + d5cfa61429d34095b68d3b10dd6eaffef442959b --- regress/cmdline/patch.sh +++ regress/cmdline/patch.sh @@ -1313,6 +1313,50 @@ EOF test_done $testroot $ret } +test_patch_reverse() { + local testroot=`test_init patch_reverse` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + cat < $testroot/wt/patch +--- alpha ++++ alpha +@@ -1 +1 @@ +-ALPHA +\ No newline at end of file ++alpha +EOF + + (cd $testroot/wt && got patch -R patch) > $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + echo "M alpha" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done $testroot $ret + return 1 + fi + + echo -n ALPHA > $testroot/wt/alpha.expected + cmp -s $testroot/wt/alpha.expected $testroot/wt/alpha + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/wt/alpha.expected $testroot/wt/alpha + fi + test_done $testroot $ret +} + test_parseargs "$@" run_test test_patch_simple_add_file run_test test_patch_simple_rm_file @@ -1335,3 +1379,4 @@ run_test test_patch_prefer_new_path run_test test_patch_no_newline run_test test_patch_strip run_test test_patch_relative_paths +run_test test_patch_reverse