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

[PATCH] More permissive "git-rm --cached" behavior without -f.

From
Matthieu Moy <matthieu.moy@imag.fr>
Date
Jul 13, 2007, 17:41 UTC
Message-ID
<11843484982037-git-send-email-Matthieu.Moy@imag.fr>
In-Reply-To
<vpq8x9k9peu.fsf@bauges.imag.fr>

In the previous behavior, "git-rm --cached" (without -f) had the same restriction as "git-rm". This forced the user to use the -f flag in situations which weren't actually dangerous, like:

$ git add foo # oops, I didn't want this $ git rm --cached foo # back to initial situation

Previously, the index had to match the file *and* the HEAD. With --cached, the index must now match the file *or* the HEAD. The behavior without --cached is unchanged, but provides better error messages.

Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
 Documentation/git-rm.txt |    3 ++-
 builtin-rm.c             |   32 ++++++++++++++++++++++++++------
 t/t3600-rm.sh            |   34 ++++++++++++++++++++++++++++++++++
 3 files changed, 62 insertions(+), 7 deletions(-)
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index 78f45dc..be61a82 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -14,7 +14,8 @@ DESCRIPTION
 Remove files from the working tree and from the index.  The
 files have to be identical to the tip of the branch, and no
 updates to its contents must have been placed in the staging
-area (aka index).
+area (aka index).  When --cached is given, the staged content has to
+match either the tip of the branch *or* the file on disk.
 
 
 OPTIONS
diff --git a/builtin-rm.c b/builtin-rm.c
index 4a0bd93..9a808c1 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -46,7 +46,7 @@ static int remove_file(const char *name)
 	return ret;
 }
 
-static int check_local_mod(unsigned char *head)
+static int check_local_mod(unsigned char *head, int index_only)
 {
 	/* items in list are already sorted in the cache order,
 	 * so we could do this a lot more efficiently by using
@@ -65,6 +65,8 @@ static int check_local_mod(unsigned char *head)
 		const char *name = list.name[i];
 		unsigned char sha1[20];
 		unsigned mode;
+		int local_changes = 0;
+		int staged_changes = 0;
 
 		pos = cache_name_pos(name, strlen(name));
 		if (pos < 0)
@@ -87,14 +89,32 @@ static int check_local_mod(unsigned char *head)
 			continue;
 		}
 		if (ce_match_stat(ce, &st, 0))
-			errs = error("'%s' has local modifications "
-				     "(hint: try -f)", ce->name);
+			local_changes = 1;
 		if (no_head
 		     || get_tree_entry(head, name, sha1, &mode)
 		     || ce->ce_mode != create_ce_mode(mode)
 		     || hashcmp(ce->sha1, sha1))
-			errs = error("'%s' has changes staged in the index "
-				     "(hint: try -f)", name);
+			staged_changes = 1;
+
+		if (local_changes && staged_changes)
+			errs = error("'%s' has staged content different "
+				     "from both the file and the HEAD\n"
+				     "(use -f to force removal)", name);
+		else if (!index_only) {
+			/* It's not dangerous to git-rm --cached a
+			 * file if the index matches the file or the
+			 * HEAD, since it means the deleted content is
+			 * still available somewhere.
+			 */
+			if (staged_changes)
+				errs = error("'%s' has changes staged in the index\n"
+					     "(use --cached to keep the file, "
+					     "or -f to force removal)", name);
+			if (local_changes)
+				errs = error("'%s' has local modifications\n"
+					     "(use --cached to keep the file, "
+					     "or -f to force removal)", name);
+		}
 	}
 	return errs;
 }
@@ -192,7 +212,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 		unsigned char sha1[20];
 		if (get_sha1("HEAD", sha1))
 			hashclr(sha1);
-		if (check_local_mod(sha1))
+		if (check_local_mod(sha1, index_only))
 			exit(1);
 	}
 
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index 13a461f..5c001aa 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -46,6 +46,40 @@ test_expect_success \
     'git rm --cached foo'
 
 test_expect_success \
+    'Test that git rm --cached foo succeeds if the index matches the file' \
+    'echo content > foo
+     git add foo
+     git rm --cached foo'
+
+test_expect_success \
+    'Test that git rm --cached foo succeeds if the index matches the file' \
+    'echo content > foo
+     git add foo
+     git commit -m foo
+     echo "other content" > foo
+     git rm --cached foo'
+
+test_expect_failure \
+    'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' \
+    'echo content > foo
+     git add foo
+     git commit -m foo
+     echo "other content" > foo
+     git add foo
+     echo "yet another content" > foo
+     git rm --cached foo'
+
+test_expect_success \
+    'Test that git rm --cached -f foo works in case where --cached only did not' \
+    'echo content > foo
+     git add foo
+     git commit -m foo
+     echo "other content" > foo
+     git add foo
+     echo "yet another content" > foo
+     git rm --cached -f foo'
+
+test_expect_success \
     'Post-check that foo exists but is not in index after git rm foo' \
     '[ -f foo ] && ! git ls-files --error-unmatch foo'
 
-- 
1.5.3.rc1.4.gaf83-dirty
Previous: Matthieu MoyNext: Jeff King
Message 20 of 37 in “git-rm isn't the inverse action of git-add”
  1. Christian JaegerJul 2, 2007
  2. Yann DirsonJul 2, 2007
  3. Christian JaegerJul 2, 2007
  4. Yann DirsonJul 2, 2007
  5. Matthieu MoyJul 2, 2007
  6. Johannes SchindelinJul 2, 2007
  7. Matthieu MoyJul 3, 2007
  8. Johannes SchindelinJul 3, 2007
  9. Matthieu MoyJul 3, 2007
  10. Johannes SchindelinJul 3, 2007
  11. Jan HudecJul 4, 2007
  12. Matthieu MoyJul 5, 2007
  13. David KastrupJul 5, 2007
  14. [RFC][PATCH] Re: git-rm isn't the inverse action of git-addMatthieu Moy, Jul 8, 2007
  15. Johannes SchindelinJul 8, 2007
  16. Matthieu MoyJul 8, 2007
  17. Johannes SchindelinJul 8, 2007
  18. Matthieu MoyJul 9, 2007
  19. Matthieu MoyJul 13, 2007
  20. More permissive "git-rm --cached" behavior without -f.Matthieu Moy, Jul 13, 2007
  21. Jeff KingJul 13, 2007
  22. Matthieu MoyJul 13, 2007
  23. Jeff KingJul 14, 2007
  24. Jakub NarebskiJul 14, 2007
  25. Junio C HamanoJul 14, 2007
  26. Junio C HamanoJul 14, 2007
  27. Matthieu MoyJul 14, 2007
  28. Christian JaegerJul 2, 2007
  29. Jeff KingJul 3, 2007
  30. Junio C HamanoJul 3, 2007
  31. Jeff KingJul 3, 2007
  32. Junio C HamanoJul 3, 2007
  33. Jeff KingJul 3, 2007
  34. Junio C HamanoJul 3, 2007
  35. Jakub NarebskiJul 11, 2007
  36. Jan HudecJul 11, 2007
  37. Junio C HamanoJul 11, 2007

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.