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

[PATCH v4 7/7] rm: honor sparse checkout patterns

From
Matheus Tavares <matheus.bernardino@usp.br>
Date
Apr 8, 2021, 20:41 UTC
Message-ID
<4459976ae9c508509c3989ba6684eecc7e051fc9.1617914011.git.matheus.bernardino@usp.br>
In-Reply-To
<cover.1617914011.git.matheus.bernardino@usp.br>

`git add` refrains from adding or updating index entries that are outside the current sparse checkout, but `git rm` doesn't follow the same restriction. This is somewhat counter-intuitive and inconsistent. So make `rm` honor the sparsity rules and advise on how to remove SKIP_WORKTREE entries just like `add` does. Also add some tests for the new behavior.

Suggested-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>
---
 Documentation/config/advice.txt  |  5 +-
 Documentation/git-rm.txt         |  4 +-
 builtin/rm.c                     | 35 +++++++++-----
 t/t3602-rm-sparse-checkout.sh    | 78 ++++++++++++++++++++++++++++++++
 t/t7011-skip-worktree-reading.sh |  5 --
 5 files changed, 108 insertions(+), 19 deletions(-)
 create mode 100755 t/t3602-rm-sparse-checkout.sh
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 851b83cf30..8b2849ff7b 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -120,6 +120,7 @@ advice.*::
 		Advice shown if a user runs the add command without providing
 		the pathspec parameter.
 	updateSparsePath::
-		Advice shown when linkgit:git-add[1] is asked to update index
-		entries outside the current sparse checkout.
+		Advice shown when either linkgit:git-add[1] or linkgit:git-rm[1]
+		is asked to update index entries outside the current sparse
+		checkout.
 --
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index ab750367fd..26e9b28470 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -23,7 +23,9 @@ branch, and no updates to their contents can be staged in the index,
 though that default behavior can be overridden with the `-f` option.
 When `--cached` is given, the staged content has to
 match either the tip of the branch or the file on disk,
-allowing the file to be removed from just the index.
+allowing the file to be removed from just the index. When
+sparse-checkouts are in use (see linkgit:git-sparse-checkout[1]),
+`git rm` will only remove paths within the sparse-checkout patterns.
 
 
 OPTIONS
diff --git a/builtin/rm.c b/builtin/rm.c
index 4858631e0f..d23a3b2164 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -5,6 +5,7 @@
  */
 #define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "builtin.h"
+#include "advice.h"
 #include "config.h"
 #include "lockfile.h"
 #include "dir.h"
@@ -254,7 +255,7 @@ static struct option builtin_rm_options[] = {
 int cmd_rm(int argc, const char **argv, const char *prefix)
 {
 	struct lock_file lock_file = LOCK_INIT;
-	int i;
+	int i, ret = 0;
 	struct pathspec pathspec;
 	char *seen;
 
@@ -295,6 +296,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
 	for (i = 0; i < active_nr; i++) {
 		const struct cache_entry *ce = active_cache[i];
+		if (ce_skip_worktree(ce))
+			continue;
 		if (!ce_path_match(&the_index, ce, &pathspec, seen))
 			continue;
 		ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
@@ -308,24 +311,34 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 	if (pathspec.nr) {
 		const char *original;
 		int seen_any = 0;
+		char *skip_worktree_seen = NULL;
+		struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
+
 		for (i = 0; i < pathspec.nr; i++) {
 			original = pathspec.items[i].original;
-			if (!seen[i]) {
-				if (!ignore_unmatch) {
-					die(_("pathspec '%s' did not match any files"),
-					    original);
-				}
-			}
-			else {
+			if (seen[i])
 				seen_any = 1;
-			}
+			else if (ignore_unmatch)
+				continue;
+			else if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
+				string_list_append(&only_match_skip_worktree, original);
+			else
+				die(_("pathspec '%s' did not match any files"), original);
+
 			if (!recursive && seen[i] == MATCHED_RECURSIVELY)
 				die(_("not removing '%s' recursively without -r"),
 				    *original ? original : ".");
 		}
 
+		if (only_match_skip_worktree.nr) {
+			advise_on_updating_sparse_paths(&only_match_skip_worktree);
+			ret = 1;
+		}
+		free(skip_worktree_seen);
+		string_list_clear(&only_match_skip_worktree, 0);
+
 		if (!seen_any)
-			exit(0);
+			exit(ret);
 	}
 
 	if (!index_only)
@@ -405,5 +418,5 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 			       COMMIT_LOCK | SKIP_IF_UNCHANGED))
 		die(_("Unable to write new index file"));
 
-	return 0;
+	return ret;
 }
diff --git a/t/t3602-rm-sparse-checkout.sh b/t/t3602-rm-sparse-checkout.sh
new file mode 100755
index 0000000000..e9e9a15c74
--- /dev/null
+++ b/t/t3602-rm-sparse-checkout.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+test_description='git rm in sparse checked out working trees'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' "
+	mkdir -p sub/dir &&
+	touch a b c sub/d sub/dir/e &&
+	git add -A &&
+	git commit -m files &&
+
+	cat >sparse_error_header <<-EOF &&
+	The following pathspecs didn't match any eligible path, but they do match index
+	entries outside the current sparse checkout:
+	EOF
+
+	cat >sparse_hint <<-EOF &&
+	hint: Disable or modify the sparsity rules if you intend to update such entries.
+	hint: Disable this message with \"git config advice.updateSparsePath false\"
+	EOF
+
+	echo b | cat sparse_error_header - >sparse_entry_b_error &&
+	cat sparse_entry_b_error sparse_hint >b_error_and_hint
+"
+
+for opt in "" -f --dry-run
+do
+	test_expect_success "rm${opt:+ $opt} does not remove sparse entries" '
+		git sparse-checkout set a &&
+		test_must_fail git rm $opt b 2>stderr &&
+		test_cmp b_error_and_hint stderr &&
+		git ls-files --error-unmatch b
+	'
+done
+
+test_expect_success 'recursive rm does not remove sparse entries' '
+	git reset --hard &&
+	git sparse-checkout set sub/dir &&
+	git rm -r sub &&
+	git status --porcelain -uno >actual &&
+	echo "D  sub/dir/e" >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'rm obeys advice.updateSparsePath' '
+	git reset --hard &&
+	git sparse-checkout set a &&
+	test_must_fail git -c advice.updateSparsePath=false rm b 2>stderr &&
+	test_cmp sparse_entry_b_error stderr
+'
+
+test_expect_success 'do not advice about sparse entries when they do not match the pathspec' '
+	git reset --hard &&
+	git sparse-checkout set a &&
+	test_must_fail git rm nonexistent 2>stderr &&
+	grep "fatal: pathspec .nonexistent. did not match any files" stderr &&
+	! grep -F -f sparse_error_header stderr
+'
+
+test_expect_success 'do not warn about sparse entries when pathspec matches dense entries' '
+	git reset --hard &&
+	git sparse-checkout set a &&
+	git rm "[ba]" 2>stderr &&
+	test_must_be_empty stderr &&
+	git ls-files --error-unmatch b &&
+	test_must_fail git ls-files --error-unmatch a
+'
+
+test_expect_success 'do not warn about sparse entries with --ignore-unmatch' '
+	git reset --hard &&
+	git sparse-checkout set a &&
+	git rm --ignore-unmatch b 2>stderr &&
+	test_must_be_empty stderr &&
+	git ls-files --error-unmatch b
+'
+
+test_done
diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh
index 26852586ac..1761a2b1b9 100755
--- a/t/t7011-skip-worktree-reading.sh
+++ b/t/t7011-skip-worktree-reading.sh
@@ -132,11 +132,6 @@ test_expect_success 'diff-files does not examine skip-worktree dirty entries' '
 	test -z "$(git diff-files -- one)"
 '
 
-test_expect_success 'git-rm succeeds on skip-worktree absent entries' '
-	setup_absent &&
-	git rm 1
-'
-
 test_expect_success 'commit on skip-worktree absent entries' '
 	git reset &&
 	setup_absent &&
-- 
2.30.1
Previous: Matheus TavaresNext: Elijah Newren
Message 28 of 32 in “add/rm: honor sparse checkout and warn on sparse paths”
  1. 0/7 add/rm: honor sparse checkout and warn on sparse pathsMatheus Tavares, Mar 12, 2021
  2. 2/7 t3705: add tests for `git add` in sparse checkoutsMatheus Tavares, Mar 12, 2021
  3. Derrick StoleeMar 23, 2021
  4. 1/7 add: include magic part of pathspec on --refresh errorMatheus Tavares, Mar 12, 2021
  5. 4/7 pathspec: allow to ignore SKIP_WORKTREE entries on index matchingMatheus Tavares, Mar 12, 2021
  6. 5/7 refresh_index(): add REFRESH_DONT_MARK_SPARSE_MATCHES flagMatheus Tavares, Mar 12, 2021
  7. Junio C HamanoMar 18, 2021
  8. Junio C HamanoMar 19, 2021
  9. Matheus Tavares BernardinoMar 19, 2021
  10. Junio C HamanoMar 19, 2021
  11. Matheus Tavares BernardinoMar 30, 2021
  12. Elijah NewrenMar 31, 2021
  13. 3/7 add: make --chmod and --renormalize honor sparse checkoutsMatheus Tavares, Mar 12, 2021
  14. 6/7 add: warn when asked to update SKIP_WORKTREE entriesMatheus Tavares, Mar 12, 2021
  15. 7/7 rm: honor sparse checkout patternsMatheus Tavares, Mar 12, 2021
  16. Ævar Arnfjörð BjarmasonMar 21, 2021
  17. Matheus Tavares BernardinoMar 22, 2021
  18. Derrick StoleeMar 23, 2021
  19. Elijah NewrenMar 13, 2021
  20. 0/7 add/rm: honor sparse checkout and warn on sparse pathsMatheus Tavares, Apr 8, 2021
  21. 1/7 add: include magic part of pathspec on --refresh errorMatheus Tavares, Apr 8, 2021
  22. 2/7 t3705: add tests for `git add` in sparse checkoutsMatheus Tavares, Apr 8, 2021
  23. Derrick StoleeApr 14, 2021
  24. 3/7 add: make --chmod and --renormalize honor sparse checkoutsMatheus Tavares, Apr 8, 2021
  25. 4/7 pathspec: allow to ignore SKIP_WORKTREE entries on index matchingMatheus Tavares, Apr 8, 2021
  26. 5/7 refresh_index(): add flag to ignore SKIP_WORKTREE entriesMatheus Tavares, Apr 8, 2021
  27. 6/7 add: warn when asked to update SKIP_WORKTREE entriesMatheus Tavares, Apr 8, 2021
  28. 7/7 rm: honor sparse checkout patternsMatheus Tavares, Apr 8, 2021
  29. Elijah NewrenApr 14, 2021
  30. Matheus Tavares BernardinoApr 14, 2021
  31. Junio C HamanoApr 16, 2021
  32. Elijah NewrenApr 16, 2021

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.