git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [RFH/RFT] git-rm: update to saner semantics
@ 2006-12-21  9:18 Junio C Hamano
  2006-12-21  9:38 ` Jakub Narebski
  0 siblings, 1 reply; 2+ messages in thread
From: Junio C Hamano @ 2006-12-21  9:18 UTC (permalink / raw)
  To: git

This updates the "git rm" command with saner semantics suggested
on the list earlier with:

	Message-ID: <Pine.LNX.4.64.0612020919400.3476@woody.osdl.org>
	Message-ID: <Pine.LNX.4.64.0612040737120.3476@woody.osdl.org>

The command still validates that the given paths all talk about
sensible paths to avoid mistakes (e.g. "git rm fiel" when the
user meant to remove "file" would error out), and it has further
safety checks described next.  The biggest difference is that
the paths are removed from both index and from the working tree
(if you have an exotic need to remove paths only from the index,
you can use the --cached option).

The command refuses to remove if the copy on the working tree
does not match the index, or if the index and the HEAD does not
match.  You can defeat this check with -f option.

This safety check has two exceptions: if the working tree file
does not exist to begin with, that technically does not match
the index but it is allowed.  This is to allow this CVS style
command sequence:

	rm <path> && git rm <path>

Also if the index is unmerged at the <path>, you can use "git rm
<path>" to declare that the result of the merge loses that path,
and the above safety check does not trigger; requiring the file
to match the index in this case forces the user to do "git
update-index file && git rm file", which is just crazy.

---

 * This has seen only very limited test, so testing, adjusting the
   test case t/t3600-rm.sh (possibly others) to the updated
   semantics and documentation updates are still needed.

   Also it lacks the "require -r for recursive behaviour" safety
   valve.  I think that is a sane thing to add.

   Volunteers?

 builtin-rm.c |  103 +++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 85 insertions(+), 18 deletions(-)

diff --git a/builtin-rm.c b/builtin-rm.c
index 33d04bd..3927d93 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -9,7 +9,7 @@
 #include "cache-tree.h"
 
 static const char builtin_rm_usage[] =
-"git-rm [-n] [-v] [-f] <filepattern>...";
+"git-rm [-n] [-f] [--cached] <filepattern>...";
 
 static struct {
 	int nr, alloc;
@@ -41,12 +41,66 @@ static int remove_file(const char *name)
 	return ret;
 }
 
+static int check_local_mod(unsigned char *head)
+{
+	/* items in list are already sorted in the cache order,
+	 * so we could do this a lot more efficiently by using
+	 * tree_desc based traversal if we wanted to, but I am
+	 * lazy, and who cares if removal of files is a tad slower
+	 * than theoretical maximum speed?
+	 */
+	int i, no_head;
+	int errs = 0;
+
+	no_head = is_null_sha1(head);
+	for (i = 0; i < list.nr; i++) {
+		struct stat st;
+		int pos;
+		struct cache_entry *ce;
+		const char *name = list.name[i];
+		unsigned char sha1[20];
+		unsigned mode;
+
+		pos = cache_name_pos(name, strlen(name));
+		if (pos < 0)
+			continue; /* removing unmerged entry */
+		ce = active_cache[pos];
+
+		if (lstat(ce->name, &st) < 0) {
+			if (errno != ENOENT)
+				fprintf(stderr, "warning: '%s': %s",
+					ce->name, strerror(errno));
+			/* It already vanished from the working tree */
+			continue;
+		}
+		else if (S_ISDIR(st.st_mode)) {
+			/* if a file was removed and it is now a
+			 * directory, that is the same as ENOENT as
+			 * far as git is concerned; we do not track
+			 * directories.
+			 */
+			continue;
+		}
+		if (ce_match_stat(ce, &st, 0))
+			errs = error("'%s' has local modifications "
+				     "(hint: try -f)", ce->name);
+		if (no_head)
+			continue;
+		get_tree_entry(head, name, sha1, &mode);
+		if (ce->ce_mode != create_ce_mode(mode) ||
+		    hashcmp(ce->sha1, sha1))
+			errs = error("'%s' has changes staged in the index "
+				     "(hint: try -f)", name);
+	}
+	return errs;
+}
+
 static struct lock_file lock_file;
 
 int cmd_rm(int argc, const char **argv, const char *prefix)
 {
 	int i, newfd;
-	int verbose = 0, show_only = 0, force = 0;
+	int show_only = 0, force = 0, index_only = 0;
 	const char **pathspec;
 	char *seen;
 
@@ -62,23 +116,18 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
 		if (*arg != '-')
 			break;
-		if (!strcmp(arg, "--")) {
+		else if (!strcmp(arg, "--")) {
 			i++;
 			break;
 		}
-		if (!strcmp(arg, "-n")) {
+		else if (!strcmp(arg, "-n"))
 			show_only = 1;
-			continue;
-		}
-		if (!strcmp(arg, "-v")) {
-			verbose = 1;
-			continue;
-		}
-		if (!strcmp(arg, "-f")) {
+		else if (!strcmp(arg, "--cached"))
+			index_only = 1;
+		else if (!strcmp(arg, "-f"))
 			force = 1;
-			continue;
-		}
-		usage(builtin_rm_usage);
+		else
+			usage(builtin_rm_usage);
 	}
 	if (argc <= i)
 		usage(builtin_rm_usage);
@@ -105,8 +154,26 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 	}
 
 	/*
+	 * If not forced, the file, the index and the HEAD (if exists)
+	 * must match; but the file can already been removed, since
+	 * this sequence is a natural "novice" way:
+	 *
+	 *	rm F; git fm F
+	 *
+	 * Further, if HEAD commit exists, "diff-index --cached" must
+	 * report no changes unless forced.
+	 */
+	if (!force) {
+		unsigned char sha1[20];
+		if (get_sha1("HEAD", sha1))
+			hashclr(sha1);
+		if (check_local_mod(sha1))
+			exit(1);
+	}
+
+	/*
 	 * First remove the names from the index: we won't commit
-	 * the index unless all of them succeed
+	 * the index unless all of them succeed.
 	 */
 	for (i = 0; i < list.nr; i++) {
 		const char *path = list.name[i];
@@ -121,14 +188,14 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 		return 0;
 
 	/*
-	 * Then, if we used "-f", remove the filenames from the
-	 * workspace. If we fail to remove the first one, we
+	 * Then, unless we used "--cache", remove the filenames from
+	 * the workspace. If we fail to remove the first one, we
 	 * abort the "git rm" (but once we've successfully removed
 	 * any file at all, we'll go ahead and commit to it all:
 	 * by then we've already committed ourselves and can't fail
 	 * in the middle)
 	 */
-	if (force) {
+	if (!index_only) {
 		int removed = 0;
 		for (i = 0; i < list.nr; i++) {
 			const char *path = list.name[i];

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [RFH/RFT] git-rm: update to saner semantics
  2006-12-21  9:18 [RFH/RFT] git-rm: update to saner semantics Junio C Hamano
@ 2006-12-21  9:38 ` Jakub Narebski
  0 siblings, 0 replies; 2+ messages in thread
From: Jakub Narebski @ 2006-12-21  9:38 UTC (permalink / raw)
  To: git

Junio C Hamano wrote:

> -"git-rm [-n] [-v] [-f] <filepattern>...";
> +"git-rm [-n] [-f] [--cached] <filepattern>...";

Why do we lost -v (for verbose)?
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2006-12-21  9:40 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2006-12-21  9:18 [RFH/RFT] git-rm: update to saner semantics Junio C Hamano
2006-12-21  9:38 ` Jakub Narebski

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).