From: Omar Polo Subject: add a dry run to got patch To: gameoftrees@openbsd.org Date: Sun, 13 Mar 2022 19:34:11 +0100 Hello, the following diff adds a dry-run mode for got patch. In this mode the patch is only faked and the changes are not really done to the work tree. I chosen -n because it stands for "nop" which I like. For comparison, Subversions seems to have --dry-run, git-apply(1) --check and patch(1) has -C, --dry-run and --check. (I don't really like to type capital letters ;-) It carries the same issues that patch(1) has: if the patchfile contains multiple diffs that affect the same file, got patch -n could report a failure even if 'got patch' would succeed. (eventually we could sort the patches and try to apply them to the same temp file to handle also this case, but I'm not sure and this is a start anyway) diff 5b67f96efbfbf2e5a3f75f6ab91e45dd3013c77f /home/op/w/got blob - 191ae2e5a9cee80ff3902ee897f67501ec3aad5e file + got/got.1 --- got/got.1 +++ got/got.1 @@ -1285,7 +1285,7 @@ option) .El .El .Tg pa -.It Cm patch Op Ar patchfile +.It Cm patch Oo Fl n Oc Op Ar patchfile .Dl Pq alias: Cm pa Apply changes from .Ar patchfile @@ -1333,6 +1333,19 @@ Such cahnges can be viewed with and can be reverted with .Cm got revert if needed. +.Pp +The options for +.Cm got patch +are as follows: +.Bl -tag -width Ds +.It Fl n +Check that the patch would apply cleanly but don't do any edits to the +worktree. +Note that if +.Ar patchfile +contains diffs that affect the same file multiple times the results won't +likely be correct. +.El .Tg rv .It Cm revert Oo Fl p Oc Oo Fl F Ar response-script Oc Oo Fl R Oc Ar path ... .Dl Pq alias: Cm rv blob - 4bcea52af617043cda2fa50d87f21effce198199 file + got/got.c --- got/got.c +++ got/got.c @@ -7132,7 +7132,7 @@ done: __dead static void usage_patch(void) { - fprintf(stderr, "usage: %s patch [patchfile]\n", + fprintf(stderr, "usage: %s patch [-n] [patchfile]\n", getprogname()); exit(1); } @@ -7192,11 +7192,14 @@ cmd_patch(int argc, char *argv[]) struct got_worktree *worktree = NULL; struct got_repository *repo = NULL; char *cwd = NULL; - int ch; + int ch, nop = 0; int patchfd; - while ((ch = getopt(argc, argv, "")) != -1) { + while ((ch = getopt(argc, argv, "n")) != -1) { switch (ch) { + case 'n': + nop = 1; + break; default: usage_patch(); /* NOTREACHED */ @@ -7244,7 +7247,7 @@ cmd_patch(int argc, char *argv[]) err(1, "pledge"); #endif - error = got_patch(patchfd, worktree, repo, &print_remove_status, + error = got_patch(patchfd, worktree, repo, nop, &print_remove_status, NULL, &add_progress, NULL, check_cancelled, NULL); done: blob - 448bb17e65d75d8d5ba94bd577f5cde4fef566bf file + include/got_patch.h --- include/got_patch.h +++ include/got_patch.h @@ -21,6 +21,6 @@ * The patch file descriptor *must* be seekable. */ const struct got_error * -got_patch(int, struct got_worktree *, struct got_repository *, +got_patch(int, struct got_worktree *, struct got_repository *, int, got_worktree_delete_cb, void *, got_worktree_checkout_cb, void *, got_cancel_cb, void *); blob - 71695e64b89d1a1b07daab61fb55d3c1883760ec file + lib/patch.c --- lib/patch.c +++ lib/patch.c @@ -66,6 +66,7 @@ struct got_patch_hunk { }; struct got_patch { + int nop; char *old; char *new; STAILQ_HEAD(, got_patch_hunk) head; @@ -399,6 +400,8 @@ patch_file(struct got_patch *p, const char *path, FILE h = STAILQ_FIRST(&p->head); if (h == NULL || STAILQ_NEXT(h, entries) != NULL) return got_error(GOT_ERR_PATCH_MALFORMED); + if (p->nop) + return NULL; for (i = 0; i < h->len; ++i) { if (fprintf(tmp, "%s", h->lines[i]+1) < 0) return got_error_from_errno("fprintf"); @@ -420,7 +423,8 @@ patch_file(struct got_patch *p, const char *path, FILE err = locate_hunk(orig, h, &pos, &lineno); if (err != NULL) goto done; - err = copy(tmp, orig, copypos, pos); + if (!p->nop) + err = copy(tmp, orig, copypos, pos); if (err != NULL) goto done; copypos = pos; @@ -446,7 +450,8 @@ patch_file(struct got_patch *p, const char *path, FILE if (err != NULL) goto done; - err = apply_hunk(tmp, h, &lineno); + if (!p->nop) + err = apply_hunk(tmp, h, &lineno); if (err != NULL) goto done; @@ -465,7 +470,7 @@ patch_file(struct got_patch *p, const char *path, FILE err = got_error_from_errno("fstat"); else if (sb.st_size != copypos) err = got_error(GOT_ERR_PATCH_DONT_APPLY); - } else if (!feof(orig)) + } else if (!p->nop && !feof(orig)) err = copy(tmp, orig, copypos, -1); done: @@ -599,13 +604,17 @@ apply_patch(struct got_worktree *worktree, struct got_ goto done; } - err = got_opentemp_named(&tmppath, &tmp, template); + if (!p->nop) + err = got_opentemp_named(&tmppath, &tmp, template); if (err) goto done; err = patch_file(p, oldpath, tmp); if (err) goto done; + if (p->nop) + goto done; + if (p->old != NULL && p->new == NULL) { err = got_worktree_schedule_delete(worktree, &oldpaths, 0, NULL, delete_cb, delete_arg, repo, 0, 0); @@ -645,7 +654,7 @@ done: const struct got_error * got_patch(int fd, struct got_worktree *worktree, struct got_repository *repo, - got_worktree_delete_cb delete_cb, void *delete_arg, + int nop, got_worktree_delete_cb delete_cb, void *delete_arg, got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb, void *cancel_arg) { @@ -695,6 +704,7 @@ got_patch(int fd, struct got_worktree *worktree, struc if (err || done) break; + p.nop = nop; err = apply_patch(worktree, repo, &p, delete_cb, delete_arg, add_cb, add_arg, cancel_cb, cancel_arg); patch_free(&p); blob - 9a300a816a79b7eacef07263e7bd7cbce706ae25 file + regress/cmdline/patch.sh --- regress/cmdline/patch.sh +++ regress/cmdline/patch.sh @@ -914,6 +914,59 @@ EOF test_done $testroot $ret } +test_patch_nop() { + local testroot=`test_init patch_nop` + + 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 ++cafe alpha +--- beta ++++ /dev/null +@@ -1 +0,0 @@ +-beta +--- gamma/delta ++++ gamma/delta.new +@@ -1 +1 @@ +-delta ++delta updated and renamed! +EOF + + (cd $testroot/wt && got patch -n patch) + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + # remove the patch to avoid the ? entry + rm $testroot/wt/patch + + (cd $testroot/wt && got status) > $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + echo -n > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done $testroot $ret +} + test_parseargs "$@" run_test test_patch_simple_add_file run_test test_patch_simple_rm_file @@ -928,3 +981,4 @@ run_test test_patch_no_patch run_test test_patch_equals_for_context run_test test_patch_rename run_test test_patch_illegal_status +run_test test_patch_nop