Commit Diff


commit - 4362cf9c24c2ad037cda602ae688bef54869f793
commit + 4e12cd97c02ae412c5a247dfaffa22ad58dd98b4
blob - 1ba9101f0a27f2b37c7a01f8d02db40c5c95a9fa
blob + eaafde8411ebca559ac74b4970f3a9ee755e26b9
--- got/got.1
+++ got/got.1
@@ -1259,7 +1259,10 @@ The options for
 are as follows:
 .Bl -tag -width Ds
 .It Fl f
-Perform the operation even if a file contains local modifications.
+Perform the operation even if a file contains local modifications,
+and do not raise an error if a specified
+.Ar path
+does not exist on disk.
 .It Fl k
 Keep affected files on disk.
 .It Fl R
blob - f551f71ac97f147d51b0c32e92dce6513394be94
blob + 16b939dba60d2210e6c75b2ffbb51a4b1f8d608a
--- got/got.c
+++ got/got.c
@@ -6981,6 +6981,7 @@ cmd_remove(int argc, char *argv[])
 	struct got_pathlist_head paths;
 	struct got_pathlist_entry *pe;
 	int ch, delete_local_mods = 0, can_recurse = 0, keep_on_disk = 0, i;
+	int ignore_missing_paths = 0;
 
 	TAILQ_INIT(&paths);
 
@@ -6988,6 +6989,7 @@ cmd_remove(int argc, char *argv[])
 		switch (ch) {
 		case 'f':
 			delete_local_mods = 1;
+			ignore_missing_paths = 1;
 			break;
 		case 'k':
 			keep_on_disk = 1;
@@ -7002,6 +7004,7 @@ cmd_remove(int argc, char *argv[])
 					delete_local_mods = 1;
 					break;
 				case GOT_STATUS_MISSING:
+					ignore_missing_paths = 1;
 					break;
 				default:
 					errx(1, "invalid status code '%c'",
@@ -7084,7 +7087,7 @@ cmd_remove(int argc, char *argv[])
 
 	error = got_worktree_schedule_delete(worktree, &paths,
 	    delete_local_mods, status_codes, print_remove_status, NULL,
-	    repo, keep_on_disk);
+	    repo, keep_on_disk, ignore_missing_paths);
 done:
 	if (repo) {
 		const struct got_error *close_err = got_repo_close(repo);
blob - 2402196b13cc52e6f489e9fd9d985074fa7dcbfa
blob + 2a33834a903f96a6f0206be4636193177a5f443d
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -194,7 +194,7 @@ const struct got_error *got_worktree_schedule_add(stru
 const struct got_error *
 got_worktree_schedule_delete(struct got_worktree *,
     struct got_pathlist_head *, int, const char *,
-    got_worktree_delete_cb, void *, struct got_repository *, int);
+    got_worktree_delete_cb, void *, struct got_repository *, int, int);
 
 /* A callback function which is used to select or reject a patch. */
 typedef const struct got_error *(*got_worktree_patch_cb)(int *, void *,
blob - 03ed920c296768d481852c8a28ea3acae1f379e1
blob + 7b9584f4e628041adfb0cb7d180a875fa9e5a922
--- lib/worktree.c
+++ lib/worktree.c
@@ -3954,6 +3954,7 @@ struct schedule_deletion_args {
 	struct got_repository *repo;
 	int delete_local_mods;
 	int keep_on_disk;
+	int ignore_missing_paths;
 	const char *status_codes;
 };
 
@@ -3968,6 +3969,12 @@ schedule_for_deletion(void *arg, unsigned char status,
 	struct got_fileindex_entry *ie = NULL;
 	struct stat sb;
 	char *ondisk_path;
+
+	if (status == GOT_STATUS_NONEXISTENT) {
+		if (a->ignore_missing_paths)
+			return NULL;
+		return got_error_set_errno(ENOENT, relpath);
+	}
 
 	ie = got_fileindex_entry_get(a->fileindex, relpath, strlen(relpath));
 	if (ie == NULL)
@@ -4018,6 +4025,10 @@ schedule_for_deletion(void *arg, unsigned char status,
 			err = got_error_path(relpath, GOT_ERR_FILE_MODIFIED);
 			goto done;
 		}
+		if (status == GOT_STATUS_MISSING && !a->ignore_missing_paths) {
+			err = got_error_set_errno(ENOENT, relpath);
+			goto done;
+		}
 		if (status != GOT_STATUS_MODIFY &&
 		    status != GOT_STATUS_MISSING) {
 			err = got_error_path(relpath, GOT_ERR_FILE_STATUS);
@@ -4073,7 +4084,7 @@ got_worktree_schedule_delete(struct got_worktree *work
     struct got_pathlist_head *paths, int delete_local_mods,
     const char *status_codes,
     got_worktree_delete_cb progress_cb, void *progress_arg,
-    struct got_repository *repo, int keep_on_disk)
+    struct got_repository *repo, int keep_on_disk, int ignore_missing_paths)
 {
 	struct got_fileindex *fileindex = NULL;
 	char *fileindex_path = NULL;
@@ -4096,6 +4107,7 @@ got_worktree_schedule_delete(struct got_worktree *work
 	sda.repo = repo;
 	sda.delete_local_mods = delete_local_mods;
 	sda.keep_on_disk = keep_on_disk;
+	sda.ignore_missing_paths = ignore_missing_paths;
 	sda.status_codes = status_codes;
 
 	TAILQ_FOREACH(pe, paths, entry) {
blob - cab753f3139225f2518ae82f6ba928625c46e9e6
blob + 8b4c659ae6120bad5862072ea9cc9fd55eaaaccd
--- regress/cmdline/rm.sh
+++ regress/cmdline/rm.sh
@@ -159,8 +159,51 @@ test_rm_and_add_elsewhere() {
 		return 1
 	fi
 
+	(cd $testroot/wt && got rm alpha > $testroot/stdout 2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "got rm command succeeded unexpectedly" >&2
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	echo -n '' > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: alpha: No such file or directory" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && got rm -f alpha > $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got rm command failed unexpectedly" >&2
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
 	echo 'D  alpha' > $testroot/stdout.expected
-	(cd $testroot/wt && got rm alpha > $testroot/stdout)
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
 
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret="$?"
@@ -507,10 +550,82 @@ test_rm_status_code() {
 	ret="$?"
 	if [ "$ret" != "0" ]; then
 		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	test_done "$testroot" "$ret"
+}
+
+test_rm_nonexistent_directory() {
+	local testroot=`test_init rm_nonexistent_directory`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	rm -r $testroot/wt/epsilon
+
+	(cd $testroot/wt && got rm epsilon > $testroot/stdout \
+		2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "got rm command succeeded unexpectedly" >&2
+		diff -u $testroot/stderr.expected $testroot/stderr
 		test_done "$testroot" "1"
 		return 1
 	fi
 
+	echo -n '' > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: epsilon: No such file or directory" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && got rm -f epsilon > $testroot/stdout \
+		2> $testroot/stderr)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got rm command failed unexpectedly" >&2
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo -n '' > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo -n '' > $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
 	test_done "$testroot" "$ret"
 }
 
@@ -525,3 +640,4 @@ run_test test_rm_directory_keep_files
 run_test test_rm_subtree
 run_test test_rm_symlink
 run_test test_rm_status_code
+run_test test_rm_nonexistent_directory