summaryrefslogtreecommitdiff
path: root/builtin/rm.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/rm.c')
-rw-r--r--builtin/rm.c125
1 files changed, 110 insertions, 15 deletions
diff --git a/builtin/rm.c b/builtin/rm.c
index b384c4c3cf..2aea3b5653 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -9,6 +9,7 @@
#include "cache-tree.h"
#include "tree-walk.h"
#include "parse-options.h"
+#include "submodule.h"
static const char * const builtin_rm_usage[] = {
N_("git rm [options] [--] <file>..."),
@@ -17,9 +18,58 @@ static const char * const builtin_rm_usage[] = {
static struct {
int nr, alloc;
- const char **name;
+ struct {
+ const char *name;
+ char is_submodule;
+ } *entry;
} list;
+static int get_ours_cache_pos(const char *path, int pos)
+{
+ int i = -pos - 1;
+
+ while ((i < active_nr) && !strcmp(active_cache[i]->name, path)) {
+ if (ce_stage(active_cache[i]) == 2)
+ return i;
+ i++;
+ }
+ return -1;
+}
+
+static int check_submodules_use_gitfiles(void)
+{
+ int i;
+ int errs = 0;
+
+ for (i = 0; i < list.nr; i++) {
+ const char *name = list.entry[i].name;
+ int pos;
+ struct cache_entry *ce;
+ struct stat st;
+
+ pos = cache_name_pos(name, strlen(name));
+ if (pos < 0) {
+ pos = get_ours_cache_pos(name, pos);
+ if (pos < 0)
+ continue;
+ }
+ ce = active_cache[pos];
+
+ if (!S_ISGITLINK(ce->ce_mode) ||
+ (lstat(ce->name, &st) < 0) ||
+ is_empty_dir(name))
+ continue;
+
+ if (!submodule_uses_gitfile(name))
+ errs = error(_("submodule '%s' (or one of its nested "
+ "submodules) uses a .git directory\n"
+ "(use 'rm -rf' if you really want to remove "
+ "it including all of its history)"), name);
+ }
+
+ return errs;
+}
+
static int check_local_mod(unsigned char *head, int index_only)
{
/*
@@ -37,15 +87,26 @@ static int check_local_mod(unsigned char *head, int index_only)
struct stat st;
int pos;
struct cache_entry *ce;
- const char *name = list.name[i];
+ const char *name = list.entry[i].name;
unsigned char sha1[20];
unsigned mode;
int local_changes = 0;
int staged_changes = 0;
pos = cache_name_pos(name, strlen(name));
- if (pos < 0)
- continue; /* removing unmerged entry */
+ if (pos < 0) {
+ /*
+ * Skip unmerged entries except for populated submodules
+ * that could lose history when removed.
+ */
+ pos = get_ours_cache_pos(name, pos);
+ if (pos < 0)
+ continue;
+
+ if (!S_ISGITLINK(active_cache[pos]->ce_mode) ||
+ is_empty_dir(name))
+ continue;
+ }
ce = active_cache[pos];
if (lstat(ce->name, &st) < 0) {
@@ -58,9 +119,10 @@ static int check_local_mod(unsigned char *head, int index_only)
/* 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.
+ * directories unless they are submodules.
*/
- continue;
+ if (!S_ISGITLINK(ce->ce_mode))
+ continue;
}
/*
@@ -80,8 +142,11 @@ static int check_local_mod(unsigned char *head, int index_only)
/*
* Is the index different from the file in the work tree?
+ * If it's a submodule, is its work tree modified?
*/
- if (ce_match_stat(ce, &st, 0))
+ if (ce_match_stat(ce, &st, 0) ||
+ (S_ISGITLINK(ce->ce_mode) &&
+ !ok_to_remove_submodule(ce->name)))
local_changes = 1;
/*
@@ -115,10 +180,18 @@ static int check_local_mod(unsigned char *head, int index_only)
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);
+ if (local_changes) {
+ if (S_ISGITLINK(ce->ce_mode) &&
+ !submodule_uses_gitfile(name)) {
+ errs = error(_("submodule '%s' (or one of its nested "
+ "submodules) uses a .git directory\n"
+ "(use 'rm -rf' if you really want to remove "
+ "it including all of its history)"), name);
+ } else
+ errs = error(_("'%s' has local modifications\n"
+ "(use --cached to keep the file, "
+ "or -f to force removal)"), name);
+ }
}
}
return errs;
@@ -173,8 +246,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
struct cache_entry *ce = active_cache[i];
if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
continue;
- ALLOC_GROW(list.name, list.nr + 1, list.alloc);
- list.name[list.nr++] = ce->name;
+ ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
+ list.entry[list.nr].name = ce->name;
+ list.entry[list.nr++].is_submodule = S_ISGITLINK(ce->ce_mode);
}
if (pathspec) {
@@ -215,6 +289,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
hashclr(sha1);
if (check_local_mod(sha1, index_only))
exit(1);
+ } else if (!index_only) {
+ if (check_submodules_use_gitfiles())
+ exit(1);
}
/*
@@ -222,7 +299,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
* the index unless all of them succeed.
*/
for (i = 0; i < list.nr; i++) {
- const char *path = list.name[i];
+ const char *path = list.entry[i].name;
if (!quiet)
printf("rm '%s'\n", path);
@@ -244,7 +321,25 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (!index_only) {
int removed = 0;
for (i = 0; i < list.nr; i++) {
- const char *path = list.name[i];
+ const char *path = list.entry[i].name;
+ if (list.entry[i].is_submodule) {
+ if (is_empty_dir(path)) {
+ if (!rmdir(path)) {
+ removed = 1;
+ continue;
+ }
+ } else {
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addstr(&buf, path);
+ if (!remove_dir_recursively(&buf, 0)) {
+ removed = 1;
+ strbuf_release(&buf);
+ continue;
+ }
+ strbuf_release(&buf);
+ /* Fallthrough and let remove_path() fail. */
+ }
+ }
if (!remove_path(path)) {
removed = 1;
continue;