Commit Diff


commit - a78810f8f51f2a0e721f8a34d621802607fc49ac
commit + 42d9d68e7b6919ea8e3bbb80057c2c11fe5594b2
blob - a3510202cfef4ac9f640bafc7d4f8e231cfa0982
blob + 7e081f1d91c2e1fda7b06f2782814027df40277d
--- got/got.c
+++ got/got.c
@@ -7245,7 +7245,7 @@ cmd_patch(int argc, char *argv[])
 #endif
 
 	error = got_patch(patchfd, worktree, repo, &print_remove_status,
-	    NULL, &add_progress, NULL);
+	    NULL, &add_progress, NULL, check_cancelled, NULL);
 
 done:
 	if (repo) {
blob - 04a23fc7f7da83078d23e8ec24820ce98aa43702
blob + 448bb17e65d75d8d5ba94bd577f5cde4fef566bf
--- include/got_patch.h
+++ include/got_patch.h
@@ -22,4 +22,5 @@
  */
 const struct got_error *
 got_patch(int, struct got_worktree *, struct got_repository *,
-    got_worktree_delete_cb, void *, got_worktree_checkout_cb, void *);
+    got_worktree_delete_cb, void *, got_worktree_checkout_cb, void *,
+    got_cancel_cb, void *);
blob - 126ca847c901495d37cbfb18e5a97a53f215178e
blob + 7f4134d7707578a9a67af1005e364da8d3fc3df8
--- lib/patch.c
+++ lib/patch.c
@@ -28,6 +28,7 @@
 #include <sys/socket.h>
 #include <sys/uio.h>
 
+#include <errno.h>
 #include <limits.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -376,44 +377,6 @@ apply_hunk(FILE *tmp, struct got_patch_hunk *h, long *
 		}
 	}
 	return NULL;
-}
-
-static const struct got_error *
-schedule_add(const char *path, struct got_worktree *worktree,
-    struct got_repository *repo, got_worktree_checkout_cb add_cb,
-    void *add_arg)
-{
-	static const struct got_error *err = NULL;
-	struct got_pathlist_head paths;
-	struct got_pathlist_entry *pe;
-
-	TAILQ_INIT(&paths);
-
-	err = got_pathlist_insert(&pe, &paths, path, NULL);
-	if (err == NULL)
-		err = got_worktree_schedule_add(worktree, &paths,
-		    add_cb, add_arg, repo, 1);
-	got_pathlist_free(&paths);
-	return err;
-}
-
-static const struct got_error *
-schedule_del(const char *path, struct got_worktree *worktree,
-    struct got_repository *repo, got_worktree_delete_cb delete_cb,
-    void *delete_arg)
-{
-	static const struct got_error *err = NULL;
-	struct got_pathlist_head paths;
-	struct got_pathlist_entry *pe;
-
-	TAILQ_INIT(&paths);
-
-	err = got_pathlist_insert(&pe, &paths, path, NULL);
-	if (err == NULL)
-		err = got_worktree_schedule_delete(worktree, &paths,
-		    0, NULL, delete_cb, delete_arg, repo, 0, 0);
-	got_pathlist_free(&paths);
-	return err;
 }
 
 static const struct got_error *
@@ -505,33 +468,131 @@ done:
 }
 
 static const struct got_error *
+build_pathlist(const char *p, char **path, struct got_pathlist_head *head,
+    struct got_worktree *worktree)
+{
+	const struct got_error *err;
+	struct got_pathlist_entry *pe;
+
+	err = got_worktree_resolve_path(path, worktree, p);
+	if (err == NULL)
+		err = got_pathlist_insert(&pe, head, *path, NULL);
+	return err;
+}
+
+static const struct got_error *
+can_rm(void *arg, unsigned char status, unsigned char staged_status,
+    const char *path, struct got_object_id *blob_id,
+    struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
+    int dirfd, const char *de_name)
+{
+	if (status == GOT_STATUS_NONEXISTENT)
+		return got_error_set_errno(ENOENT, path);
+	if (status != GOT_STATUS_NO_CHANGE &&
+	    status != GOT_STATUS_ADD &&
+	    status != GOT_STATUS_MODIFY &&
+	    status != GOT_STATUS_MODE_CHANGE)
+		return got_error_path(path, GOT_ERR_FILE_STATUS);
+	if (staged_status == GOT_STATUS_DELETE)
+		return got_error_path(path, GOT_ERR_FILE_STATUS);
+	return NULL;
+}
+
+static const struct got_error *
+can_add(void *arg, unsigned char status, unsigned char staged_status,
+    const char *path, struct got_object_id *blob_id,
+    struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
+    int dirfd, const char *de_name)
+{
+	if (status != GOT_STATUS_NONEXISTENT)
+		return got_error_path(path, GOT_ERR_FILE_STATUS);
+	return NULL;
+}
+
+static const struct got_error *
+can_edit(void *arg, unsigned char status, unsigned char staged_status,
+    const char *path, struct got_object_id *blob_id,
+    struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
+    int dirfd, const char *de_name)
+{
+	if (status == GOT_STATUS_NONEXISTENT)
+		return got_error_set_errno(ENOENT, path);
+	if (status != GOT_STATUS_NO_CHANGE &&
+	    status != GOT_STATUS_ADD &&
+	    status != GOT_STATUS_MODIFY)
+		return got_error_path(path, GOT_ERR_FILE_STATUS);
+	if (staged_status == GOT_STATUS_DELETE)
+		return got_error_path(path, GOT_ERR_FILE_STATUS);
+	return NULL;
+}
+
+static const struct got_error *
+check_file_status(struct got_patch *p, int file_renamed,
+    struct got_worktree *worktree, struct got_repository *repo,
+    struct got_pathlist_head *old, struct got_pathlist_head *new,
+    got_cancel_cb cancel_cb, void *cancel_arg)
+{
+	static const struct got_error *err;
+
+	if (p->old != NULL && p->new == NULL)
+		return got_worktree_status(worktree, old, repo, 0,
+		    can_rm, NULL, cancel_cb, cancel_arg);
+	else if (file_renamed) {
+		err = got_worktree_status(worktree, old, repo, 0,
+		    can_rm, NULL, cancel_cb, cancel_arg);
+		if (err)
+			return err;
+		return got_worktree_status(worktree, new, repo, 0,
+		    can_add, NULL, cancel_cb, cancel_arg);
+	} else if (p->old == NULL)
+		return got_worktree_status(worktree, new, repo, 0,
+		    can_add, NULL, cancel_cb, cancel_arg);
+	else
+		return got_worktree_status(worktree, new, repo, 0,
+		    can_edit, NULL, cancel_cb, cancel_arg);
+}
+
+static const struct got_error *
 apply_patch(struct got_worktree *worktree, struct got_repository *repo,
     struct got_patch *p, got_worktree_delete_cb delete_cb, void *delete_arg,
-    got_worktree_checkout_cb add_cb, void *add_arg)
+    got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb,
+    void *cancel_arg)
 {
 	const struct got_error *err = NULL;
+	struct got_pathlist_head oldpaths, newpaths;
 	int file_renamed = 0;
 	char *oldpath = NULL, *newpath = NULL;
 	char *tmppath = NULL, *template = NULL;
 	FILE *tmp = NULL;
 
-	err = got_worktree_resolve_path(&oldpath, worktree,
-	    p->old != NULL ? p->old : p->new);
+	TAILQ_INIT(&oldpaths);
+	TAILQ_INIT(&newpaths);
+
+	err = build_pathlist(p->old != NULL ? p->old : p->new, &oldpath,
+	    &oldpaths, worktree);
 	if (err)
 		goto done;
 
-	err = got_worktree_resolve_path(&newpath, worktree,
-	    p->new != NULL ? p->new : p->old);
+	err = build_pathlist(p->new != NULL ? p->new : p->old, &newpath,
+	    &newpaths, worktree);
 	if (err)
 		goto done;
 
+	if (p->old != NULL && p->new != NULL && strcmp(p->old, p->new))
+		file_renamed = 1;
+
+	err = check_file_status(p, file_renamed, worktree, repo, &oldpaths,
+	    &newpaths, cancel_cb, cancel_arg);
+	if (err)
+		goto done;
+
 	if (p->old != NULL && p->new == NULL) {
 		/*
 		 * special case: delete a file.  don't try to match
 		 * the lines but just schedule the removal.
 		 */
-		err = schedule_del(p->old, worktree, repo, delete_cb,
-		    delete_arg);
+		err = got_worktree_schedule_delete(worktree, &oldpaths,
+		    0, NULL, delete_cb, delete_arg, repo, 0, 0);
 		goto done;
 	}
 
@@ -553,26 +614,27 @@ apply_patch(struct got_worktree *worktree, struct got_
 		goto done;
 	}
 
-	file_renamed = p->old != NULL && strcmp(p->old, p->new);
 	if (file_renamed) {
-		err = schedule_del(oldpath, worktree, repo, delete_cb,
-		    delete_arg);
+		err = got_worktree_schedule_delete(worktree, &oldpaths,
+		    0, NULL, delete_cb, delete_arg, repo, 0, 0);
 		if (err == NULL)
-			err = schedule_add(newpath, worktree, repo,
-			    add_cb, add_arg);
+			err = got_worktree_schedule_add(worktree, &newpaths,
+			    add_cb, add_arg, repo, 1);
 	} else if (p->old == NULL)
-		err = schedule_add(newpath, worktree, repo, add_cb,
-		    add_arg);
+		err = got_worktree_schedule_add(worktree, &newpaths,
+		    add_cb, add_arg, repo, 1);
 	else
 		printf("M  %s\n", oldpath); /* XXX */
 
 done:
-	if (err != NULL && (file_renamed || p->old == NULL))
+	if (err != NULL && newpath != NULL && (file_renamed || p->old == NULL))
 		unlink(newpath);
 	free(template);
 	if (tmppath != NULL)
 		unlink(tmppath);
 	free(tmppath);
+	got_pathlist_free(&oldpaths);
+	got_pathlist_free(&newpaths);
 	free(oldpath);
 	free(newpath);
 	return err;
@@ -581,7 +643,8 @@ 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,
-    got_worktree_checkout_cb add_cb, void *add_arg)
+    got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb,
+    void *cancel_arg)
 {
 	const struct got_error *err = NULL;
 	struct imsgbuf *ibuf;
@@ -630,7 +693,7 @@ got_patch(int fd, struct got_worktree *worktree, struc
 			break;
 
 		err = apply_patch(worktree, repo, &p, delete_cb, delete_arg,
-		    add_cb, add_arg);
+		    add_cb, add_arg, cancel_cb, cancel_arg);
 		patch_free(&p);
 		if (err)
 			break;
blob - 5916921f260f5a0eaefdbf6036b813fbf3570dd5
blob + 36c9ef55d45f97cd15c3f787c35d5a7623def72a
--- regress/cmdline/patch.sh
+++ regress/cmdline/patch.sh
@@ -685,6 +685,7 @@ EOF
 		test_done $testroot $ret
 		return 1
 	fi
+	rm $testroot/wt/eta
 
 	cat <<EOF > $testroot/wt/patch
 --- alpha
@@ -729,7 +730,152 @@ EOF
 	fi
 	test_done $testroot $ret
 }
+
+test_patch_illegal_status() {
+	local testroot=`test_init patch_illegal_status`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	# edit an non-existent and unknown file
+	cat <<EOF > $testroot/wt/patch
+--- iota
++++ iota
+@@ -1 +1 @@
+- iota
++ IOTA
+EOF
 
+	(cd $testroot/wt && got patch patch) > /dev/null \
+		2> $testroot/stderr
+	ret=$?
+	if [ $ret -eq 0 ]; then
+		echo "edited a missing file" >&2
+		test_done $testroot $ret
+		return 1
+	fi
+
+	echo "got: iota: No such file or directory" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done $testroot $ret
+		return 1
+	fi
+
+	# create iota and re-try
+	echo iota > $testroot/wt/iota
+
+	(cd $testroot/wt && got patch patch) > /dev/null \
+		2> $testroot/stderr
+	ret=$?
+	if [ $ret -eq 0 ]; then
+		echo "patched an unknown file" >&2
+		test_done $testroot $ret
+		return 1
+	fi
+
+	echo "got: iota: file has unexpected status" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done $testroot $ret
+		return 1
+	fi
+	
+	rm $testroot/wt/iota
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	# edit obstructed file
+	rm $testroot/wt/alpha
+	mkdir $testroot/wt/alpha
+	cat <<EOF > $testroot/wt/patch
+--- alpha
++++ alpha
+@@ -1 +1,2 @@
+ alpha
++was edited
+EOF
+
+	(cd $testroot/wt && got patch patch) > /dev/null \
+		2> $testroot/stderr
+	ret=$?
+	if [ $ret -eq 0 ]; then
+		echo "edited a missing file" >&2
+		test_done $testroot $ret
+		return 1
+	fi
+
+	echo "got: alpha: file has unexpected status" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done $testroot $ret
+		return 1
+	fi
+
+	# delete an unknown file
+	cat <<EOF > $testroot/wt/patch
+--- iota
++++ /dev/null
+@@ -1 +0,0 @@
+-iota
+EOF
+
+	(cd $testroot/wt && got patch patch) > /dev/null \
+		2> $testroot/stderr
+	ret=$?
+	if [ $ret -eq 0 ]; then
+		echo "deleted a missing file?" >&2
+		test_done $testroot $ret
+		return 1
+	fi
+
+	echo "got: iota: No such file or directory" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret=$?
+	if [ $ret -eq 0 ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done $testroot $ret
+		return 1
+	fi
+
+	# try again with iota in place but still not registered
+	echo iota > $testroot/wt/iota
+	(cd $testroot/wt && got patch patch) > /dev/null \
+		2> $testroot/stderr
+	ret=$?
+	if [ $ret -eq 0 ]; then
+		echo "deleted an unversioned file?" >&2
+		test_done $testroot $ret
+		return 1
+	fi
+
+	echo "got: iota: file has unexpected status" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret=$?
+	if [ $ret -eq 0 ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+	fi
+	test_done $testroot $ret
+}
+
 test_parseargs "$@"
 run_test test_patch_simple_add_file
 run_test test_patch_simple_rm_file
@@ -743,3 +889,4 @@ run_test test_patch_malformed
 run_test test_patch_no_patch
 run_test test_patch_equals_for_context
 run_test test_patch_rename
+run_test test_patch_illegal_status