diff options
Diffstat (limited to 'builtin-rm.c')
-rw-r--r-- | builtin-rm.c | 52 |
1 files changed, 42 insertions, 10 deletions
diff --git a/builtin-rm.c b/builtin-rm.c index e06640cf8d..c11f455858 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -31,7 +31,8 @@ static void add_list(const char *name) static int check_local_mod(unsigned char *head, int index_only) { - /* items in list are already sorted in the cache order, + /* + * 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 @@ -71,24 +72,55 @@ static int check_local_mod(unsigned char *head, int index_only) */ continue; } + + /* + * "rm" of a path that has changes need to be treated + * carefully not to allow losing local changes + * accidentally. A local change could be (1) file in + * work tree is different since the index; and/or (2) + * the user staged a content that is different from + * the current commit in the index. + * + * In such a case, you would need to --force the + * removal. However, "rm --cached" (remove only from + * the index) is safe if the index matches the file in + * the work tree or the HEAD commit, as it means that + * the content being removed is available elsewhere. + */ + + /* + * Is the index different from the file in the work tree? + */ if (ce_match_stat(ce, &st, 0)) local_changes = 1; + + /* + * Is the index different from the HEAD commit? By + * definition, before the very initial commit, + * anything staged in the index is treated by the same + * way as changed from the HEAD. + */ if (no_head || get_tree_entry(head, name, sha1, &mode) || ce->ce_mode != create_ce_mode(mode) || hashcmp(ce->sha1, sha1)) 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); + /* + * If the index does not match the file in the work + * tree and if it does not match the HEAD commit + * either, (1) "git rm" without --cached definitely + * will lose information; (2) "git rm --cached" will + * lose information unless it is about removing an + * "intent to add" entry. + */ + if (local_changes && staged_changes) { + if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD)) + 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, " |