Commit Diff


commit - 081470ac52a4d68384a33f36bdd0d3096d6cf772
commit + 766841c2970cb5bef66c9c69201b231d0eefb120
blob - 8f049181b8b59970cb560bc5e0623a8015811931
blob + 19122e372a158d8a95216bb854924e9a25225a76
--- got/got.1
+++ got/got.1
@@ -1105,7 +1105,7 @@ With -R, add files even if they match a
 .Cm got status
 ignore pattern.
 .El
-.It Cm remove Oo Fl f Oc Oo Fl k Oc Oo Fl R Oc Ar path ...
+.It Cm remove Oo Fl f Oc Oo Fl k Oc Oo Fl R Oc Oo Fl s Ar status-codes Oc Ar path ...
 Remove versioned files from a work tree and schedule them for deletion
 from the repository in the next commit.
 .Pp
@@ -1124,6 +1124,18 @@ If this option is not specified,
 will refuse to run if a specified
 .Ar path
 is a directory.
+.It Fl s Ar status-codes
+Only delete files with a modification status matching one of the
+single-character status codes contained in the
+.Ar status-codes
+argument.
+The following status codes may be specified:
+.Bl -column YXZ description
+.It M Ta modified file (this implies the
+.Fl f
+option)
+.It ! Ta versioned file expected on disk but missing
+.El
 .El
 .It Cm rm
 Short alias for
blob - 93fe92b8cf1c50517bdedc66c2b0be234cb08074
blob + c344b324b3663021f87618a272fccebb36b409cf
--- got/got.c
+++ got/got.c
@@ -6022,8 +6022,8 @@ done:
 __dead static void
 usage_remove(void)
 {
-	fprintf(stderr, "usage: %s remove [-f] [-k] [-R] path ...\n",
-	    getprogname());
+	fprintf(stderr, "usage: %s remove [-f] [-k] [-R] [-s status-codes] "
+	    "path ...\n", getprogname());
 	exit(1);
 }
 
@@ -6047,14 +6047,15 @@ cmd_remove(int argc, char *argv[])
 	const struct got_error *error = NULL;
 	struct got_worktree *worktree = NULL;
 	struct got_repository *repo = NULL;
+	const char *status_codes = NULL;
 	char *cwd = NULL;
 	struct got_pathlist_head paths;
 	struct got_pathlist_entry *pe;
-	int ch, delete_local_mods = 0, can_recurse = 0, keep_on_disk = 0;
+	int ch, delete_local_mods = 0, can_recurse = 0, keep_on_disk = 0, i;
 
 	TAILQ_INIT(&paths);
 
-	while ((ch = getopt(argc, argv, "fkR")) != -1) {
+	while ((ch = getopt(argc, argv, "fkRs:")) != -1) {
 		switch (ch) {
 		case 'f':
 			delete_local_mods = 1;
@@ -6065,6 +6066,21 @@ cmd_remove(int argc, char *argv[])
 		case 'R':
 			can_recurse = 1;
 			break;
+		case 's':
+			for (i = 0; i < strlen(optarg); i++) {
+				switch (optarg[i]) {
+				case GOT_STATUS_MODIFY:
+					delete_local_mods = 1;
+					break;
+				case GOT_STATUS_MISSING:
+					break;
+				default:
+					errx(1, "invalid status code '%c'",
+					    optarg[i]);
+				}
+			}
+			status_codes = optarg;
+			break;
 		default:
 			usage_remove();
 			/* NOTREACHED */
@@ -6138,7 +6154,8 @@ cmd_remove(int argc, char *argv[])
 	}
 
 	error = got_worktree_schedule_delete(worktree, &paths,
-	    delete_local_mods, print_remove_status, NULL, repo, keep_on_disk);
+	    delete_local_mods, status_codes, print_remove_status, NULL,
+	    repo, keep_on_disk);
 done:
 	if (repo)
 		got_repo_close(repo);
blob - f0548eaf6e23287e3754b08de478a43d1c8206c2
blob + c655d810c3ca855bdbd7d38b7191e1243efb8b91
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -187,8 +187,8 @@ 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, got_worktree_delete_cb, void *,
-    struct got_repository *, int);
+    struct got_pathlist_head *, int, const char *,
+    got_worktree_delete_cb, void *, struct got_repository *, 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 - 68b5face374018032f08f3badf8b7728e33e799d
blob + 8afc4c2596b52c558eca6fd9e0ac8678040d308f
--- lib/worktree.c
+++ lib/worktree.c
@@ -3742,6 +3742,7 @@ struct schedule_deletion_args {
 	struct got_repository *repo;
 	int delete_local_mods;
 	int keep_on_disk;
+	const char *status_codes;
 };
 
 static const struct got_error *
@@ -3776,6 +3777,28 @@ schedule_for_deletion(void *arg, unsigned char status,
 	if (err)
 		goto done;
 
+	if (a->status_codes) {
+		size_t ncodes = strlen(a->status_codes);
+		int i;
+		for (i = 0; i < ncodes ; i++) {
+			if (status == a->status_codes[i])
+				break;
+		}
+		if (i == ncodes) { 
+			/* Do not delete files in non-matching status. */
+			free(ondisk_path);
+			return NULL;
+		}
+		if (a->status_codes[i] != GOT_STATUS_MODIFY &&
+		    a->status_codes[i] != GOT_STATUS_MISSING) {
+			static char msg[64];
+			snprintf(msg, sizeof(msg),
+			    "invalid status code '%c'", a->status_codes[i]);
+			err = got_error_msg(GOT_ERR_FILE_STATUS, msg);
+			goto done;
+		}
+	}
+
 	if (status != GOT_STATUS_NO_CHANGE) {
 		if (status == GOT_STATUS_DELETE)
 			goto done;
@@ -3837,6 +3860,7 @@ done:
 const struct got_error *
 got_worktree_schedule_delete(struct got_worktree *worktree,
     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)
 {
@@ -3861,6 +3885,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.status_codes = status_codes;
 
 	TAILQ_FOREACH(pe, paths, entry) {
 		err = worktree_status(worktree, pe->path, fileindex, repo,
blob - 73d86eda6228d31f0ebefc6ba49d0625ab6833b0
blob + e159de382cd06a0f707167bfb65330fac8ddc2be
--- regress/cmdline/rm.sh
+++ regress/cmdline/rm.sh
@@ -429,15 +429,92 @@ function test_rm_symlink {
 	echo 'D  nonexistent.link' >> $testroot/stdout.expected
 	(cd $testroot/wt && got rm alpha.link epsilon.link passwd.link \
 		epsilon/beta.link nonexistent.link > $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
+function test_rm_status_code {
+	local testroot=`test_init rm_status_code`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "modified beta" > $testroot/wt/beta
+
+	echo "got: invalid status code 'x'" > $testroot/stderr.expected
+	(cd $testroot/wt && got rm -s Mx beta 2>$testroot/stderr)
+
+	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
+
+	rm $testroot/wt/epsilon/zeta # put file into 'missing' status
+
+	echo 'D  epsilon/zeta' > $testroot/stdout.expected
+	(cd $testroot/wt && got rm -R -s '!' . >$testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
 
+	if [ ! -e $testroot/wt/beta ]; then
+		echo "file beta was unexpectedly removed from disk" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	# put file into 'missing' status again
+	(cd $testroot/wt && got revert epsilon/zeta > /dev/null)
+	rm $testroot/wt/epsilon/zeta
+
+	echo 'D  beta' > $testroot/stdout.expected
+	echo 'D  epsilon/zeta' >> $testroot/stdout.expected
+	(cd $testroot/wt && got rm -R -s 'M!' . >$testroot/stdout)
+
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret="$?"
 	if [ "$ret" != "0" ]; then
 		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "1"
+		return 1
 	fi
+
+	if [ -e $testroot/wt/beta ]; then
+		echo "removed file beta still exists on disk" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	echo 'D  beta' > $testroot/stdout.expected
+	echo 'D  epsilon/zeta' >> $testroot/stdout.expected
+	(cd $testroot/wt && got status > $testroot/stdout)
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "1"
+		return 1
+	fi
+
 	test_done "$testroot" "$ret"
 }
 
+
 test_parseargs "$@"
 run_test test_rm_basic
 run_test test_rm_with_local_mods
@@ -447,3 +524,4 @@ run_test test_rm_directory
 run_test test_rm_directory_keep_files
 run_test test_rm_subtree
 run_test test_rm_symlink
+run_test test_rm_status_code