"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Omar Polo <op@omarpolo.com>
Subject:
add a dry run to got patch
To:
gameoftrees@openbsd.org
Date:
Sun, 13 Mar 2022 19:34:11 +0100

Download raw body.

Thread
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 <<EOF > $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