git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 2/8] rm: support the --pathspec-from-file option

From
AGAlexandr Miloslavskiy via GitGitGadget <gitgitgadget@gmail.com>
Date
Feb 10, 2020, 14:45 UTC
Message-ID
<7ccbab52e51423a9ba74c0cab77448ceabb9dcdc.1581345948.git.gitgitgadget@gmail.com>
In-Reply-To
<pull.530.v2.git.1581345948.gitgitgadget@gmail.com>
From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
Decisions taken for simplicity:
1) It is not allowed to pass pathspec in both args and file.

`if (!argc)` block was adapted to work with --pathspec-from-file. For that, I also had to parse pathspec earlier. Now it happens before `read_cache()` / `hold_locked_index()` / `setup_work_tree()`, which sounds fine to me.

In case of empty pathspec, there is now a clear error message instead of showing usage. As a consequence, exit code has also changed. Judging from [1] it doesn't seem that showing usage in this case was important (according to commit message, it was to avoid segfault), and it doesn't fit into how other commands react to empty pathspec. Finally, the new error message is easier to understand.

[1] Commit 7612a1ef ("git-rm: honor -n flag" 2006-06-09)
Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
---
 Documentation/git-rm.txt    | 17 +++++++-
 builtin/rm.c                | 28 ++++++++++---
 t/t3601-rm-pathspec-file.sh | 79 +++++++++++++++++++++++++++++++++++++
 3 files changed, 117 insertions(+), 7 deletions(-)
 create mode 100755 t/t3601-rm-pathspec-file.sh
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index e02a08e5ef..ab750367fd 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -8,7 +8,9 @@ git-rm - Remove files from the working tree and from the index
 SYNOPSIS
 --------
 [verse]
-'git rm' [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <pathspec>...
+'git rm' [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch]
+	  [--quiet] [--pathspec-from-file=<file> [--pathspec-file-nul]]
+	  [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -73,6 +75,19 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 	`git rm` normally outputs one line (in the form of an `rm` command)
 	for each file removed. This option suppresses that output.
 
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and all other characters are taken
+	literally (including newlines and quotes).
+
 
 REMOVING FILES THAT HAVE DISAPPEARED FROM THE FILESYSTEM
 --------------------------------------------------------
diff --git a/builtin/rm.c b/builtin/rm.c
index 19ce95a901..4858631e0f 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -235,7 +235,8 @@ static int check_local_mod(struct object_id *head, int index_only)
 }
 
 static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
-static int ignore_unmatch = 0;
+static int ignore_unmatch = 0, pathspec_file_nul;
+static char *pathspec_from_file;
 
 static struct option builtin_rm_options[] = {
 	OPT__DRY_RUN(&show_only, N_("dry run")),
@@ -245,6 +246,8 @@ static struct option builtin_rm_options[] = {
 	OPT_BOOL('r', NULL,             &recursive,  N_("allow recursive removal")),
 	OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch,
 				N_("exit with a zero status even if nothing matched")),
+	OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+	OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
 	OPT_END(),
 };
 
@@ -259,8 +262,24 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
 	argc = parse_options(argc, argv, prefix, builtin_rm_options,
 			     builtin_rm_usage, 0);
-	if (!argc)
-		usage_with_options(builtin_rm_usage, builtin_rm_options);
+
+	parse_pathspec(&pathspec, 0,
+		       PATHSPEC_PREFER_CWD,
+		       prefix, argv);
+
+	if (pathspec_from_file) {
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_CWD,
+				    prefix, pathspec_from_file, pathspec_file_nul);
+	} else if (pathspec_file_nul) {
+		die(_("--pathspec-file-nul requires --pathspec-from-file"));
+	}
+
+	if (!pathspec.nr)
+		die(_("No pathspec was given. Which files should I remove?"));
 
 	if (!index_only)
 		setup_work_tree();
@@ -270,9 +289,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 	if (read_cache() < 0)
 		die(_("index file corrupt"));
 
-	parse_pathspec(&pathspec, 0,
-		       PATHSPEC_PREFER_CWD,
-		       prefix, argv);
 	refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &pathspec, NULL, NULL);
 
 	seen = xcalloc(pathspec.nr, 1);
diff --git a/t/t3601-rm-pathspec-file.sh b/t/t3601-rm-pathspec-file.sh
new file mode 100755
index 0000000000..4542a0f02f
--- /dev/null
+++ b/t/t3601-rm-pathspec-file.sh
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+test_description='rm --pathspec-from-file'
+
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success setup '
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+	git add fileA.t fileB.t fileC.t fileD.t &&
+	git commit -m "files" &&
+	
+	git tag checkpoint
+'
+
+restore_checkpoint () {
+	git reset --hard checkpoint
+}
+
+verify_expect () {
+	git status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'simplest' '
+	restore_checkpoint &&
+
+	cat >expect <<-\EOF &&
+	D  fileA.t
+	EOF
+
+	echo fileA.t | git rm --pathspec-from-file=- &&
+	verify_expect
+'
+
+test_expect_success '--pathspec-file-nul' '
+	restore_checkpoint &&
+
+	cat >expect <<-\EOF &&
+	D  fileA.t
+	D  fileB.t
+	EOF
+
+	printf "fileA.t\0fileB.t\0" | git rm --pathspec-from-file=- --pathspec-file-nul &&
+	verify_expect
+'
+
+test_expect_success 'only touches what was listed' '
+	restore_checkpoint &&
+
+	cat >expect <<-\EOF &&
+	D  fileB.t
+	D  fileC.t
+	EOF
+
+	printf "fileB.t\nfileC.t\n" | git rm --pathspec-from-file=- &&
+	verify_expect
+'
+
+test_expect_success 'error conditions' '
+	restore_checkpoint &&
+	echo fileA.t >list &&
+
+	test_must_fail git rm --pathspec-from-file=list -- fileA.t 2>err &&
+	test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+	test_must_fail git rm --pathspec-file-nul 2>err &&
+	test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err &&
+	
+	>empty_list &&
+	test_must_fail git rm --pathspec-from-file=empty_list 2>err &&
+	test_i18ngrep -e "No pathspec was given. Which files should I remove?" err
+'
+
+test_done
-- 
gitgitgadget
Previous: Alexandr Miloslavskiy via GitGitGadgetNext: Junio C Hamano
Message 23 of 41 in “Support --pathspec-from-file in rm, stash”
  1. 0/8 Support --pathspec-from-file in rm, stashAlexandr Miloslavskiy via GitGitGadget, Jan 16, 2020
  2. 1/8 doc: rm: synchronize <pathspec> descriptionAlexandr Miloslavskiy via GitGitGadget, Jan 16, 2020
  3. Junio C HamanoJan 21, 2020
  4. 2/8 rm: support the --pathspec-from-file optionAlexandr Miloslavskiy via GitGitGadget, Jan 16, 2020
  5. Junio C HamanoJan 21, 2020
  6. Alexandr MiloslavskiyFeb 10, 2020
  7. Junio C HamanoFeb 10, 2020
  8. Alexandr MiloslavskiyFeb 17, 2020
  9. 4/8 doc: stash: split options from description (2)Alexandr Miloslavskiy via GitGitGadget, Jan 16, 2020
  10. Junio C HamanoJan 21, 2020
  11. Alexandr MiloslavskiyFeb 10, 2020
  12. 5/8 doc: stash: document more optionsAlexandr Miloslavskiy via GitGitGadget, Jan 16, 2020
  13. Junio C HamanoJan 21, 2020
  14. 6/8 doc: stash: synchronize <pathspec> descriptionAlexandr Miloslavskiy via GitGitGadget, Jan 16, 2020
  15. Junio C HamanoJan 21, 2020
  16. 7/8 stash: eliminate crude option parsingAlexandr Miloslavskiy via GitGitGadget, Jan 16, 2020
  17. 8/8 stash push: support the --pathspec-from-file optionAlexandr Miloslavskiy via GitGitGadget, Jan 16, 2020
  18. 3/8 doc: stash: split options from description (1)Alexandr Miloslavskiy via GitGitGadget, Jan 16, 2020
  19. 0/8 Support --pathspec-from-file in rm, stashAlexandr Miloslavskiy via GitGitGadget, Feb 10, 2020
  20. 5/8 doc: stash: document more optionsAlexandr Miloslavskiy via GitGitGadget, Feb 10, 2020
  21. 3/8 doc: stash: split options from description (1)Alexandr Miloslavskiy via GitGitGadget, Feb 10, 2020
  22. 1/8 doc: rm: synchronize <pathspec> descriptionAlexandr Miloslavskiy via GitGitGadget, Feb 10, 2020
  23. 2/8 rm: support the --pathspec-from-file optionAlexandr Miloslavskiy via GitGitGadget, Feb 10, 2020
  24. Junio C HamanoFeb 10, 2020
  25. Alexandr MiloslavskiyFeb 17, 2020
  26. Junio C HamanoFeb 17, 2020
  27. Junio C HamanoFeb 17, 2020
  28. 4/8 doc: stash: split options from description (2)Alexandr Miloslavskiy via GitGitGadget, Feb 10, 2020
  29. 6/8 doc: stash: synchronize <pathspec> descriptionAlexandr Miloslavskiy via GitGitGadget, Feb 10, 2020
  30. 8/8 stash push: support the --pathspec-from-file optionAlexandr Miloslavskiy via GitGitGadget, Feb 10, 2020
  31. 7/8 stash: eliminate crude option parsingAlexandr Miloslavskiy via GitGitGadget, Feb 10, 2020
  32. 0/8 Support --pathspec-from-file in rm, stashAlexandr Miloslavskiy via GitGitGadget, Feb 17, 2020
  33. 1/8 doc: rm: synchronize <pathspec> descriptionAlexandr Miloslavskiy via GitGitGadget, Feb 17, 2020
  34. 2/8 rm: support the --pathspec-from-file optionAlexandr Miloslavskiy via GitGitGadget, Feb 17, 2020
  35. Alexandr MiloslavskiyFeb 17, 2020
  36. 3/8 doc: stash: split options from description (1)Alexandr Miloslavskiy via GitGitGadget, Feb 17, 2020
  37. 5/8 doc: stash: document more optionsAlexandr Miloslavskiy via GitGitGadget, Feb 17, 2020
  38. 8/8 stash push: support the --pathspec-from-file optionAlexandr Miloslavskiy via GitGitGadget, Feb 17, 2020
  39. 4/8 doc: stash: split options from description (2)Alexandr Miloslavskiy via GitGitGadget, Feb 17, 2020
  40. 7/8 stash: eliminate crude option parsingAlexandr Miloslavskiy via GitGitGadget, Feb 17, 2020
  41. 6/8 doc: stash: synchronize <pathspec> descriptionAlexandr Miloslavskiy via GitGitGadget, Feb 17, 2020

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.