{"thread":{"id":"44745","subject":"[PATCHv5 0/4] git-rm absorbs submodule git directory before deletion","startedAt":"2016-12-20T23:20:23Z","lastAt":"2016-12-27T21:56:27Z","messageCount":13,"participants":["Stefan Beller","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"308179","messageId":"20161220232012.15997-1-sbeller@google.com","threadId":"44745","inReplyTo":null,"subject":"[PATCHv5 0/4] git-rm absorbs submodule git directory before deletion","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-20T23:20:08Z","receivedAt":"2016-12-20T23:20:23Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"v5:\n* removed the patch for adding {run,start,finish}_or_die\n* added one more flag to the function ok_to_remove_submodule\n  (if die on error is ok)\n* renamed ok_to_remove_submodule to bad_to_remove_submodule to signal\n  the error case better.\n\nv4:\n* reworded commit messages of the last 2 patches\n* introduced a new patch introducing {run,start,finish}_command_or_die\n* found an existing function in dir.h to use to remove a directory\n  which deals gracefully with the cornercases, that Junio pointed out.\n  \nv3:\n* removed the patch to enhance ok_to_remove_submodule to absorb the submodule\n  if needed\n* Removed all the error reporting from git-rm that was related to submodule\n  git directories not absorbed.\n* instead just absorb the git repositories or let the absorb function die\n  with an appropriate error message.\n\nv2:\n* new base where to apply the patch:\n  sb/submodule-embed-gitdir merged with sb/t3600-cleanup.\n  I got merge conflicts and resolved them this way:\n#@@@ -709,9 -687,10 +687,9 @@@ test_expect_success 'checking out a com\n#          git commit -m \"submodule removal\" submod &&\n#          git checkout HEAD^ &&\n#          git submodule update &&\n#-         git checkout -q HEAD^ 2>actual &&\n#+         git checkout -q HEAD^ &&\n#          git checkout -q master 2>actual &&\n# -        echo \"warning: unable to rmdir submod: Directory not empty\" >expected &&\n# -        test_i18ncmp expected actual &&\n# +        test_i18ngrep \"^warning: unable to rmdir submod:\" actual &&\n#          git status -s submod >actual &&\n#          echo \"?? submod/\" >expected &&\n#          test_cmp expected actual &&\n#\n\n* improved commit message in \"ok_to_remove_submodule: absorb the submodule git dir\"\n  (David Turner offered me some advice on how to write better English off list)\n* simplified code in last patch:\n  -> dropped wrong comment for fallthrough\n  -> moved redundant code out of both bodies of an if-clause.\n* Fixed last patchs commit message to have \"or_die\" instead of or_dir.\n\nv1:\nThe \"checkout --recurse-submodules\" series got too large to comfortably send\nit out for review, so I had to break it up into smaller series'; this is the\nfirst subseries, but it makes sense on its own.\n\nThis series teaches git-rm to absorb the git directory of a submodule instead\nof failing and complaining about the git directory preventing deletion.\n\nIt applies on origin/sb/submodule-embed-gitdir.\n\nAny feedback welcome!\n\nThanks,\nStefan\n\n\nStefan Beller (4):\n  submodule.h: add extern keyword to functions\n  submodule: modernize ok_to_remove_submodule to use argv_array\n  submodule: rename and add flags to ok_to_remove_submodule\n  rm: absorb a submodules git dir before deletion\n\n builtin/rm.c  | 83 +++++++++++++++--------------------------------------------\n submodule.c   | 57 ++++++++++++++++++++++++++--------------\n submodule.h   | 59 ++++++++++++++++++++++++------------------\n t/t3600-rm.sh | 39 +++++++++++-----------------\n 4 files changed, 108 insertions(+), 130 deletions(-)\n\n-- \n2.11.0.rc2.53.gb7b3fba.dirty\n\n"},{"id":"308180","messageId":"20161220232012.15997-2-sbeller@google.com","threadId":"44745","inReplyTo":"20161220232012.15997-1-sbeller@google.com","subject":"[PATCHv5 1/4] submodule.h: add extern keyword to functions","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-20T23:20:09Z","receivedAt":"2016-12-20T23:20:30Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"As the upcoming series will add a lot of functions to the submodule\nheader, let's first make the header consistent to the rest of the project\nby adding the extern keyword to functions.\n\nAs per the CodingGuidelines we try to stay below 80 characters per line,\nso adapt all those functions to stay below 80 characters that are already\nusing more than one line.  Those function using just one line are better\nkept in one line than breaking them up into multiple lines just for the\ngoal of staying below the character limit as it makes grepping\nfor functions easier if they are one liners.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n submodule.h | 55 ++++++++++++++++++++++++++++++-------------------------\n 1 file changed, 30 insertions(+), 25 deletions(-)\n\ndiff --git a/submodule.h b/submodule.h\nindex 6229054b99..61fb610749 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -29,50 +29,55 @@ struct submodule_update_strategy {\n };\n #define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}\n \n-int is_staging_gitmodules_ok(void);\n-int update_path_in_gitmodules(const char *oldpath, const char *newpath);\n-int remove_path_from_gitmodules(const char *path);\n-void stage_updated_gitmodules(void);\n-void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,\n+extern int is_staging_gitmodules_ok(void);\n+extern int update_path_in_gitmodules(const char *oldpath, const char *newpath);\n+extern int remove_path_from_gitmodules(const char *path);\n+extern void stage_updated_gitmodules(void);\n+extern void set_diffopt_flags_from_submodule_config(struct diff_options *,\n \t\tconst char *path);\n-int submodule_config(const char *var, const char *value, void *cb);\n-void gitmodules_config(void);\n-int parse_submodule_update_strategy(const char *value,\n+extern int submodule_config(const char *var, const char *value, void *cb);\n+extern void gitmodules_config(void);\n+extern int parse_submodule_update_strategy(const char *value,\n \t\tstruct submodule_update_strategy *dst);\n-const char *submodule_strategy_to_string(const struct submodule_update_strategy *s);\n-void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);\n-void show_submodule_summary(FILE *f, const char *path,\n+extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s);\n+extern void handle_ignore_submodules_arg(struct diff_options *, const char *);\n+extern void show_submodule_summary(FILE *f, const char *path,\n \t\tconst char *line_prefix,\n \t\tstruct object_id *one, struct object_id *two,\n \t\tunsigned dirty_submodule, const char *meta,\n \t\tconst char *del, const char *add, const char *reset);\n-void show_submodule_inline_diff(FILE *f, const char *path,\n+extern void show_submodule_inline_diff(FILE *f, const char *path,\n \t\tconst char *line_prefix,\n \t\tstruct object_id *one, struct object_id *two,\n \t\tunsigned dirty_submodule, const char *meta,\n \t\tconst char *del, const char *add, const char *reset,\n \t\tconst struct diff_options *opt);\n-void set_config_fetch_recurse_submodules(int value);\n-void check_for_new_submodule_commits(unsigned char new_sha1[20]);\n-int fetch_populated_submodules(const struct argv_array *options,\n+extern void set_config_fetch_recurse_submodules(int value);\n+extern void check_for_new_submodule_commits(unsigned char new_sha1[20]);\n+extern int fetch_populated_submodules(const struct argv_array *options,\n \t\t\t       const char *prefix, int command_line_option,\n \t\t\t       int quiet, int max_parallel_jobs);\n-unsigned is_submodule_modified(const char *path, int ignore_untracked);\n-int submodule_uses_gitfile(const char *path);\n-int ok_to_remove_submodule(const char *path);\n-int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],\n-\t\t    const unsigned char a[20], const unsigned char b[20], int search);\n-int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,\n-\t\tstruct string_list *needs_pushing);\n-int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name);\n-int parallel_submodules(void);\n+extern unsigned is_submodule_modified(const char *path, int ignore_untracked);\n+extern int submodule_uses_gitfile(const char *path);\n+extern int ok_to_remove_submodule(const char *path);\n+extern int merge_submodule(unsigned char result[20], const char *path,\n+\t\t\t   const unsigned char base[20],\n+\t\t\t   const unsigned char a[20],\n+\t\t\t   const unsigned char b[20], int search);\n+extern int find_unpushed_submodules(unsigned char new_sha1[20],\n+\t\t\t\t    const char *remotes_name,\n+\t\t\t\t    struct string_list *needs_pushing);\n+extern int push_unpushed_submodules(unsigned char new_sha1[20],\n+\t\t\t\t    const char *remotes_name);\n+extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);\n+extern int parallel_submodules(void);\n \n /*\n  * Prepare the \"env_array\" parameter of a \"struct child_process\" for executing\n  * a submodule by clearing any repo-specific envirionment variables, but\n  * retaining any config in the environment.\n  */\n-void prepare_submodule_repo_env(struct argv_array *out);\n+extern void prepare_submodule_repo_env(struct argv_array *out);\n \n #define ABSORB_GITDIR_RECURSE_SUBMODULES (1<<0)\n extern void absorb_git_dir_into_superproject(const char *prefix,\n-- \n2.11.0.rc2.53.gb7b3fba.dirty\n\n"},{"id":"308181","messageId":"20161220232012.15997-3-sbeller@google.com","threadId":"44745","inReplyTo":"20161220232012.15997-1-sbeller@google.com","subject":"[PATCHv5 2/4] submodule: modernize ok_to_remove_submodule to use argv_array","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-20T23:20:10Z","receivedAt":"2016-12-20T23:20:39Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Instead of constructing the NULL terminated array ourselves, we\nshould make use of the argv_array infrastructure.\n\nWhile at it, adapt the error messages to reflect the actual invocation.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n submodule.c | 14 ++++----------\n 1 file changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/submodule.c b/submodule.c\nindex 45ccfb7ab4..9f0b544ebe 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1023,13 +1023,6 @@ int ok_to_remove_submodule(const char *path)\n {\n \tssize_t len;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tconst char *argv[] = {\n-\t\t\"status\",\n-\t\t\"--porcelain\",\n-\t\t\"-u\",\n-\t\t\"--ignore-submodules=none\",\n-\t\tNULL,\n-\t};\n \tstruct strbuf buf = STRBUF_INIT;\n \tint ok_to_remove = 1;\n \n@@ -1039,14 +1032,15 @@ int ok_to_remove_submodule(const char *path)\n \tif (!submodule_uses_gitfile(path))\n \t\treturn 0;\n \n-\tcp.argv = argv;\n+\targv_array_pushl(&cp.args, \"status\", \"--porcelain\", \"-u\",\n+\t\t\t\t   \"--ignore-submodules=none\", NULL);\n \tprepare_submodule_repo_env(&cp.env_array);\n \tcp.git_cmd = 1;\n \tcp.no_stdin = 1;\n \tcp.out = -1;\n \tcp.dir = path;\n \tif (start_command(&cp))\n-\t\tdie(\"Could not run 'git status --porcelain -uall --ignore-submodules=none' in submodule %s\", path);\n+\t\tdie(_(\"could not run 'git status --porcelain -u --ignore-submodules=none' in submodule %s\"), path);\n \n \tlen = strbuf_read(&buf, cp.out, 1024);\n \tif (len > 2)\n@@ -1054,7 +1048,7 @@ int ok_to_remove_submodule(const char *path)\n \tclose(cp.out);\n \n \tif (finish_command(&cp))\n-\t\tdie(\"'git status --porcelain -uall --ignore-submodules=none' failed in submodule %s\", path);\n+\t\tdie(_(\"'git status --porcelain -u --ignore-submodules=none' failed in submodule %s\"), path);\n \n \tstrbuf_release(&buf);\n \treturn ok_to_remove;\n-- \n2.11.0.rc2.53.gb7b3fba.dirty\n\n"},{"id":"308182","messageId":"20161220232012.15997-4-sbeller@google.com","threadId":"44745","inReplyTo":"20161220232012.15997-1-sbeller@google.com","subject":"[PATCHv5 3/4] submodule: rename and add flags to ok_to_remove_submodule","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-20T23:20:11Z","receivedAt":"2016-12-20T23:20:42Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"In different contexts the question \"Is it ok to delete a submodule?\"\nmay be answered differently.\n\nIn 293ab15eea (submodule: teach rm to remove submodules unless they\ncontain a git directory, 2012-09-26) a case was made that we can safely\nignore ignored untracked files for removal as we explicitely ask for the\nremoval of the submodule.\n\nIn a later patch we want to remove submodules even when the user doesn't\nexplicitly ask for it (e.g. checking out a tree-ish in which the submodule\ndoesn't exist).  In that case we want to be more careful when it comes\nto deletion of untracked files. As of this patch it is unclear how this\nwill be implemented exactly, so we'll offer flags in which the caller\ncan specify how the different untracked files ought to be handled.\n\nAs the flags allow the function to not die on an error when spawning\na child process, we need to find an appropriate return code for the\ncase when the child process could not be started. As in that case we\ncannot tell if the submodule is ok to remove, we'd want to return 'false'.\n\nAs only 0 is understood as false, rename the function to invert the\nmeaning, i.e. the return code of 0 signals the removal of the submodule\nis fine, and other values can be used to return a more precise answer\nwhat went wrong.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n builtin/rm.c |  4 +++-\n submodule.c  | 49 +++++++++++++++++++++++++++++++++++++------------\n submodule.h  |  6 +++++-\n 3 files changed, 45 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/rm.c b/builtin/rm.c\nindex 3f3e24eb36..5a5a66272b 100644\n--- a/builtin/rm.c\n+++ b/builtin/rm.c\n@@ -187,7 +187,9 @@ static int check_local_mod(struct object_id *head, int index_only)\n \t\t */\n \t\tif (ce_match_stat(ce, &st, 0) ||\n \t\t    (S_ISGITLINK(ce->ce_mode) &&\n-\t\t     !ok_to_remove_submodule(ce->name)))\n+\t\t     bad_to_remove_submodule(ce->name,\n+\t\t\t\tSUBMODULE_REMOVAL_DIE_ON_ERROR |\n+\t\t\t\tSUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED)))\n \t\t\tlocal_changes = 1;\n \n \t\t/*\ndiff --git a/submodule.c b/submodule.c\nindex 9f0b544ebe..1cc04d24e5 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1019,39 +1019,64 @@ int submodule_uses_gitfile(const char *path)\n \treturn 1;\n }\n \n-int ok_to_remove_submodule(const char *path)\n+/*\n+ * Check if it is a bad idea to remove a submodule, i.e. if we'd lose data\n+ * when doing so.\n+ *\n+ * Return 1 if we'd lose data, return 0 if the removal is fine,\n+ * and negative values for errors.\n+ */\n+int bad_to_remove_submodule(const char *path, unsigned flags)\n {\n \tssize_t len;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tint ok_to_remove = 1;\n+\tint ret = 0;\n \n \tif (!file_exists(path) || is_empty_dir(path))\n-\t\treturn 1;\n+\t\treturn 0;\n \n \tif (!submodule_uses_gitfile(path))\n-\t\treturn 0;\n+\t\treturn 1;\n \n-\targv_array_pushl(&cp.args, \"status\", \"--porcelain\", \"-u\",\n+\targv_array_pushl(&cp.args, \"status\", \"--porcelain\",\n \t\t\t\t   \"--ignore-submodules=none\", NULL);\n+\n+\tif (flags & SUBMODULE_REMOVAL_IGNORE_UNTRACKED)\n+\t\targv_array_push(&cp.args, \"-uno\");\n+\telse\n+\t\targv_array_push(&cp.args, \"-uall\");\n+\n+\tif (!(flags & SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED))\n+\t\targv_array_push(&cp.args, \"--ignored\");\n+\n \tprepare_submodule_repo_env(&cp.env_array);\n \tcp.git_cmd = 1;\n \tcp.no_stdin = 1;\n \tcp.out = -1;\n \tcp.dir = path;\n-\tif (start_command(&cp))\n-\t\tdie(_(\"could not run 'git status --porcelain -u --ignore-submodules=none' in submodule %s\"), path);\n+\tif (start_command(&cp)) {\n+\t\tif (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR)\n+\t\t\tdie(_(\"could not start 'git status in submodule '%s'\"),\n+\t\t\t\tpath);\n+\t\tret = -1;\n+\t\tgoto out;\n+\t}\n \n \tlen = strbuf_read(&buf, cp.out, 1024);\n \tif (len > 2)\n-\t\tok_to_remove = 0;\n+\t\tret = 1;\n \tclose(cp.out);\n \n-\tif (finish_command(&cp))\n-\t\tdie(_(\"'git status --porcelain -u --ignore-submodules=none' failed in submodule %s\"), path);\n-\n+\tif (finish_command(&cp)) {\n+\t\tif (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR)\n+\t\t\tdie(_(\"could not run 'git status in submodule '%s'\"),\n+\t\t\t\tpath);\n+\t\tret = -1;\n+\t}\n+out:\n \tstrbuf_release(&buf);\n-\treturn ok_to_remove;\n+\treturn ret;\n }\n \n static int find_first_merges(struct object_array *result, const char *path,\ndiff --git a/submodule.h b/submodule.h\nindex 61fb610749..21b1569413 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -59,7 +59,11 @@ extern int fetch_populated_submodules(const struct argv_array *options,\n \t\t\t       int quiet, int max_parallel_jobs);\n extern unsigned is_submodule_modified(const char *path, int ignore_untracked);\n extern int submodule_uses_gitfile(const char *path);\n-extern int ok_to_remove_submodule(const char *path);\n+\n+#define SUBMODULE_REMOVAL_DIE_ON_ERROR (1<<0)\n+#define SUBMODULE_REMOVAL_IGNORE_UNTRACKED (1<<1)\n+#define SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED (1<<2)\n+extern int bad_to_remove_submodule(const char *path, unsigned flags);\n extern int merge_submodule(unsigned char result[20], const char *path,\n \t\t\t   const unsigned char base[20],\n \t\t\t   const unsigned char a[20],\n-- \n2.11.0.rc2.53.gb7b3fba.dirty\n\n"},{"id":"308183","messageId":"20161220232012.15997-5-sbeller@google.com","threadId":"44745","inReplyTo":"20161220232012.15997-1-sbeller@google.com","subject":"[PATCHv5 4/4] rm: absorb a submodules git dir before deletion","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-20T23:20:12Z","receivedAt":"2016-12-20T23:20:44Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"When deleting a submodule, we need to keep the actual git directory around,\nsuch that we do not lose local changes in there and at a later checkout\nof the submodule we don't need to clone it again.\n\nNow that the functionality is available to absorb the git directory of a\nsubmodule, rewrite the checking in git-rm to not complain, but rather\nrelocate the git directories inside the superproject.\n\nAn alternative solution was discussed to have a function\n`depopulate_submodule`. That would couple the check for its git directory\nand possible relocation before the the removal, such that it is less\nlikely to miss the check in the future.  But the indirection with such\na function added seemed also complex. The reason for that was that this\npossible move of the git directory was also implemented in\n`ok_to_remove_submodule`, such that this function could truthfully\nanswer whether it is ok to remove the submodule.\n\nThe solution proposed here defers all these checks to the caller.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n builtin/rm.c  | 79 ++++++++++++++---------------------------------------------\n t/t3600-rm.sh | 39 ++++++++++++-----------------\n 2 files changed, 33 insertions(+), 85 deletions(-)\n\ndiff --git a/builtin/rm.c b/builtin/rm.c\nindex 5a5a66272b..6f2001b0eb 100644\n--- a/builtin/rm.c\n+++ b/builtin/rm.c\n@@ -59,27 +59,9 @@ static void print_error_files(struct string_list *files_list,\n \t}\n }\n \n-static void error_removing_concrete_submodules(struct string_list *files, int *errs)\n-{\n-\tprint_error_files(files,\n-\t\t\t  Q_(\"the following submodule (or one of its nested \"\n-\t\t\t     \"submodules)\\n\"\n-\t\t\t     \"uses a .git directory:\",\n-\t\t\t     \"the following submodules (or one of their nested \"\n-\t\t\t     \"submodules)\\n\"\n-\t\t\t     \"use a .git directory:\", files->nr),\n-\t\t\t  _(\"\\n(use 'rm -rf' if you really want to remove \"\n-\t\t\t    \"it including all of its history)\"),\n-\t\t\t  errs);\n-\tstring_list_clear(files, 0);\n-}\n-\n-static int check_submodules_use_gitfiles(void)\n+static void submodules_absorb_gitdir_if_needed(const char *prefix)\n {\n \tint i;\n-\tint errs = 0;\n-\tstruct string_list files = STRING_LIST_INIT_NODUP;\n-\n \tfor (i = 0; i < list.nr; i++) {\n \t\tconst char *name = list.entry[i].name;\n \t\tint pos;\n@@ -99,12 +81,9 @@ static int check_submodules_use_gitfiles(void)\n \t\t\tcontinue;\n \n \t\tif (!submodule_uses_gitfile(name))\n-\t\t\tstring_list_append(&files, name);\n+\t\t\tabsorb_git_dir_into_superproject(prefix, name,\n+\t\t\t\tABSORB_GITDIR_RECURSE_SUBMODULES);\n \t}\n-\n-\terror_removing_concrete_submodules(&files, &errs);\n-\n-\treturn errs;\n }\n \n static int check_local_mod(struct object_id *head, int index_only)\n@@ -120,7 +99,6 @@ static int check_local_mod(struct object_id *head, int index_only)\n \tint errs = 0;\n \tstruct string_list files_staged = STRING_LIST_INIT_NODUP;\n \tstruct string_list files_cached = STRING_LIST_INIT_NODUP;\n-\tstruct string_list files_submodule = STRING_LIST_INIT_NODUP;\n \tstruct string_list files_local = STRING_LIST_INIT_NODUP;\n \n \tno_head = is_null_oid(head);\n@@ -219,13 +197,8 @@ static int check_local_mod(struct object_id *head, int index_only)\n \t\telse if (!index_only) {\n \t\t\tif (staged_changes)\n \t\t\t\tstring_list_append(&files_cached, name);\n-\t\t\tif (local_changes) {\n-\t\t\t\tif (S_ISGITLINK(ce->ce_mode) &&\n-\t\t\t\t    !submodule_uses_gitfile(name))\n-\t\t\t\t\tstring_list_append(&files_submodule, name);\n-\t\t\t\telse\n-\t\t\t\t\tstring_list_append(&files_local, name);\n-\t\t\t}\n+\t\t\tif (local_changes)\n+\t\t\t\tstring_list_append(&files_local, name);\n \t\t}\n \t}\n \tprint_error_files(&files_staged,\n@@ -247,8 +220,6 @@ static int check_local_mod(struct object_id *head, int index_only)\n \t\t\t  &errs);\n \tstring_list_clear(&files_cached, 0);\n \n-\terror_removing_concrete_submodules(&files_submodule, &errs);\n-\n \tprint_error_files(&files_local,\n \t\t\t  Q_(\"the following file has local modifications:\",\n \t\t\t     \"the following files have local modifications:\",\n@@ -342,6 +313,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t\t\texit(0);\n \t}\n \n+\tsubmodules_absorb_gitdir_if_needed(prefix);\n+\n \t/*\n \t * If not forced, the file, the index and the HEAD (if exists)\n \t * must match; but the file can already been removed, since\n@@ -358,9 +331,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t\t\toidclr(&oid);\n \t\tif (check_local_mod(&oid, index_only))\n \t\t\texit(1);\n-\t} else if (!index_only) {\n-\t\tif (check_submodules_use_gitfiles())\n-\t\t\texit(1);\n \t}\n \n \t/*\n@@ -389,32 +359,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t */\n \tif (!index_only) {\n \t\tint removed = 0, gitmodules_modified = 0;\n-\t\tstruct strbuf buf = STRBUF_INIT;\n \t\tfor (i = 0; i < list.nr; i++) {\n \t\t\tconst char *path = list.entry[i].name;\n \t\t\tif (list.entry[i].is_submodule) {\n-\t\t\t\tif (is_empty_dir(path)) {\n-\t\t\t\t\tif (!rmdir(path)) {\n-\t\t\t\t\t\tremoved = 1;\n-\t\t\t\t\t\tif (!remove_path_from_gitmodules(path))\n-\t\t\t\t\t\t\tgitmodules_modified = 1;\n-\t\t\t\t\t\tcontinue;\n-\t\t\t\t\t}\n-\t\t\t\t} else {\n-\t\t\t\t\tstrbuf_reset(&buf);\n-\t\t\t\t\tstrbuf_addstr(&buf, path);\n-\t\t\t\t\tif (!remove_dir_recursively(&buf, 0)) {\n-\t\t\t\t\t\tremoved = 1;\n-\t\t\t\t\t\tif (!remove_path_from_gitmodules(path))\n-\t\t\t\t\t\t\tgitmodules_modified = 1;\n-\t\t\t\t\t\tstrbuf_release(&buf);\n-\t\t\t\t\t\tcontinue;\n-\t\t\t\t\t} else if (!file_exists(path))\n-\t\t\t\t\t\t/* Submodule was removed by user */\n-\t\t\t\t\t\tif (!remove_path_from_gitmodules(path))\n-\t\t\t\t\t\t\tgitmodules_modified = 1;\n-\t\t\t\t\t/* Fallthrough and let remove_path() fail. */\n-\t\t\t\t}\n+\t\t\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\t\t\tstrbuf_addstr(&buf, path);\n+\t\t\t\tif (remove_dir_recursively(&buf, 0))\n+\t\t\t\t\tdie(_(\"could not remove '%s'\"), path);\n+\t\t\t\tstrbuf_release(&buf);\n+\n+\t\t\t\tremoved = 1;\n+\t\t\t\tif (!remove_path_from_gitmodules(path))\n+\t\t\t\t\tgitmodules_modified = 1;\n+\t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!remove_path(path)) {\n \t\t\t\tremoved = 1;\n@@ -423,7 +381,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t\t\tif (!removed)\n \t\t\t\tdie_errno(\"git rm: '%s'\", path);\n \t\t}\n-\t\tstrbuf_release(&buf);\n \t\tif (gitmodules_modified)\n \t\t\tstage_updated_gitmodules();\n \t}\ndiff --git a/t/t3600-rm.sh b/t/t3600-rm.sh\nindex bcbb680651..5aa6db584c 100755\n--- a/t/t3600-rm.sh\n+++ b/t/t3600-rm.sh\n@@ -569,26 +569,22 @@ test_expect_success 'rm of a conflicted unpopulated submodule succeeds' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'rm of a populated submodule with a .git directory fails even when forced' '\n+test_expect_success 'rm of a populated submodule with a .git directory migrates git dir' '\n \tgit checkout -f master &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \t(cd submod &&\n \t\trm .git &&\n \t\tcp -R ../.git/modules/sub .git &&\n-\t\tGIT_WORK_TREE=. git config --unset core.worktree\n+\t\tGIT_WORK_TREE=. git config --unset core.worktree &&\n+\t\trm -r ../.git/modules/sub\n \t) &&\n-\ttest_must_fail git rm submod &&\n-\ttest -d submod &&\n-\ttest -d submod/.git &&\n-\tgit status -s -uno --ignore-submodules=none >actual &&\n-\t! test -s actual &&\n-\ttest_must_fail git rm -f submod &&\n-\ttest -d submod &&\n-\ttest -d submod/.git &&\n+\tgit rm submod 2>output.err &&\n+\t! test -d submod &&\n+\t! test -d submod/.git &&\n \tgit status -s -uno --ignore-submodules=none >actual &&\n-\t! test -s actual &&\n-\trm -rf submod\n+\ttest -s actual &&\n+\ttest_i18ngrep Migrating output.err\n '\n \n cat >expect.deepmodified <<EOF\n@@ -667,24 +663,19 @@ test_expect_success 'rm of a populated nested submodule with a nested .git direc\n \tgit submodule update --recursive &&\n \t(cd submod/subsubmod &&\n \t\trm .git &&\n-\t\tcp -R ../../.git/modules/sub/modules/sub .git &&\n+\t\tmv ../../.git/modules/sub/modules/sub .git &&\n \t\tGIT_WORK_TREE=. git config --unset core.worktree\n \t) &&\n-\ttest_must_fail git rm submod &&\n-\ttest -d submod &&\n-\ttest -d submod/subsubmod/.git &&\n-\tgit status -s -uno --ignore-submodules=none >actual &&\n-\t! test -s actual &&\n-\ttest_must_fail git rm -f submod &&\n-\ttest -d submod &&\n-\ttest -d submod/subsubmod/.git &&\n+\tgit rm submod 2>output.err &&\n+\t! test -d submod &&\n+\t! test -d submod/subsubmod/.git &&\n \tgit status -s -uno --ignore-submodules=none >actual &&\n-\t! test -s actual &&\n-\trm -rf submod\n+\ttest -s actual &&\n+\ttest_i18ngrep Migrating output.err\n '\n \n test_expect_success 'checking out a commit after submodule removal needs manual updates' '\n-\tgit commit -m \"submodule removal\" submod &&\n+\tgit commit -m \"submodule removal\" submod .gitmodules &&\n \tgit checkout HEAD^ &&\n \tgit submodule update &&\n \tgit checkout -q HEAD^ &&\n-- \n2.11.0.rc2.53.gb7b3fba.dirty\n\n"},{"id":"308364","messageId":"xmqqr34uchuq.fsf@gitster.mtv.corp.google.com","threadId":"44745","inReplyTo":"20161220232012.15997-4-sbeller@google.com","subject":"Re: [PATCHv5 3/4] submodule: rename and add flags to ok_to_remove_submodule","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-12-27T00:53:49Z","receivedAt":"2016-12-27T00:54:01Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> As only 0 is understood as false, rename the function to invert the\n> meaning, i.e. the return code of 0 signals the removal of the submodule\n> is fine, and other values can be used to return a more precise answer\n> what went wrong.\n\nMakes sense to rename it as that will catch all the callers that\ndepend on the old semantics and name.\n\n> -\tif (start_command(&cp))\n> -\t\tdie(_(\"could not run 'git status --porcelain -u --ignore-submodules=none' in submodule %s\"), path);\n> +\tif (start_command(&cp)) {\n> +\t\tif (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR)\n> +\t\t\tdie(_(\"could not start 'git status in submodule '%s'\"),\n> +\t\t\t\tpath);\n> +\t\tret = -1;\n> +\t\tgoto out;\n> +\t}\n\nThis new codepath that does not die will not leak anything, as\na failed start_command() should release its argv[] and env[].\n\n>  \tlen = strbuf_read(&buf, cp.out, 1024);\n>  \tif (len > 2)\n> -\t\tok_to_remove = 0;\n> +\t\tret = 1;\n\nNot a new problem but is it obvious why the comparison of \"len\" is\nagainst \"2\"?  This may deserve a one-liner comment.\n\nOtherwise looks good to me.\n"},{"id":"308365","messageId":"xmqqmvfich2e.fsf@gitster.mtv.corp.google.com","threadId":"44745","inReplyTo":"20161220232012.15997-5-sbeller@google.com","subject":"Re: [PATCHv5 4/4] rm: absorb a submodules git dir before deletion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-12-27T01:10:49Z","receivedAt":"2016-12-27T01:11:12Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> @@ -342,6 +313,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>  \t\t\texit(0);\n>  \t}\n>  \n> +\tsubmodules_absorb_gitdir_if_needed(prefix);\n> +\n>  \t/*\n>  \t * If not forced, the file, the index and the HEAD (if exists)\n>  \t * must match; but the file can already been removed, since\n> @@ -358,9 +331,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>  \t\t\toidclr(&oid);\n>  \t\tif (check_local_mod(&oid, index_only))\n>  \t\t\texit(1);\n> -\t} else if (!index_only) {\n> -\t\tif (check_submodules_use_gitfiles())\n> -\t\t\texit(1);\n>  \t}\n>  \n\nHmph.  It may be a bit strange to see an \"index-only\" remove to\ntouch working tree, no?  Yet submodules_absorb_gitdir_if_needed() is\nunconditionally called above, which feels somewhat unexpected. \n\n> @@ -389,32 +359,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>  \t */\n>  \tif (!index_only) {\n>  \t\tint removed = 0, gitmodules_modified = 0;\n>  \t\tfor (i = 0; i < list.nr; i++) {\n>  \t\t\tconst char *path = list.entry[i].name;\n>  \t\t\tif (list.entry[i].is_submodule) {\n> +\t\t\t\tstruct strbuf buf = STRBUF_INIT;\n> +\n> +\t\t\t\tstrbuf_addstr(&buf, path);\n> +\t\t\t\tif (remove_dir_recursively(&buf, 0))\n> +\t\t\t\t\tdie(_(\"could not remove '%s'\"), path);\n> +\t\t\t\tstrbuf_release(&buf);\n> +\n> +\t\t\t\tremoved = 1;\n> +\t\t\t\tif (!remove_path_from_gitmodules(path))\n> +\t\t\t\t\tgitmodules_modified = 1;\n> +\t\t\t\tcontinue;\n>  \t\t\t}\n\nI do not see any behaviour change from the original (not quoted\nhere), but it is somewhat surprising that \"git rm ./submodule\" does\nnot really check if the submodule has local modifications and files\nthat are not even added before remove_dir_recursively() is called.\n\nOr perhaps I am reading the code incorrectly and such a check is\ndone elsewhere?\n"},{"id":"308366","messageId":"xmqqinq6cgxa.fsf@gitster.mtv.corp.google.com","threadId":"44745","inReplyTo":"20161220232012.15997-2-sbeller@google.com","subject":"Re: [PATCHv5 1/4] submodule.h: add extern keyword to functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-12-27T01:13:53Z","receivedAt":"2016-12-27T01:14:19Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> As the upcoming series will add a lot of functions to the submodule\n> header, let's first make the header consistent to the rest of the project\n> by adding the extern keyword to functions.\n\nThis may be the right thing to do in the longer term but a patch\nlike this adds a lot of unnecessary work on the integrator when\nthere are _other_ topics that are in flight.  I'll see how bad the\nconflicts are while merging the topic to 'pu'.\n\nThanks.\n"},{"id":"308394","messageId":"CAGZ79kbpq3WPk-sSoVh8s0FUanctEjHH92LfpoBGYV2WhfEV-w@mail.gmail.com","threadId":"44745","inReplyTo":"xmqqr34uchuq.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCHv5 3/4] submodule: rename and add flags to ok_to_remove_submodule","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-27T17:55:27Z","receivedAt":"2016-12-27T17:55:33Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Dec 26, 2016 at 4:53 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Stefan Beller <sbeller@google.com> writes:\n>\n>> As only 0 is understood as false, rename the function to invert the\n>> meaning, i.e. the return code of 0 signals the removal of the submodule\n>> is fine, and other values can be used to return a more precise answer\n>> what went wrong.\n>\n> Makes sense to rename it as that will catch all the callers that\n> depend on the old semantics and name.\n>\n>> -     if (start_command(&cp))\n>> -             die(_(\"could not run 'git status --porcelain -u --ignore-submodules=none' in submodule %s\"), path);\n>> +     if (start_command(&cp)) {\n>> +             if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR)\n>> +                     die(_(\"could not start 'git status in submodule '%s'\"),\n>> +                             path);\n>> +             ret = -1;\n>> +             goto out;\n>> +     }\n>\n> This new codepath that does not die will not leak anything, as\n> a failed start_command() should release its argv[] and env[].\n>\n>>       len = strbuf_read(&buf, cp.out, 1024);\n>>       if (len > 2)\n>> -             ok_to_remove = 0;\n>> +             ret = 1;\n>\n> Not a new problem but is it obvious why the comparison of \"len\" is\n> against \"2\"?  This may deserve a one-liner comment.\n>\n> Otherwise looks good to me.\n\nI took the check \"len > 2\" from the other occurrence in submodule.c.\nWhen thinking about it (and inspecting the man page for\nporcelain status), I think just checking != 0 is sufficient.\n\nSo in a reroll I'll change that to just\n\n    if (len)\n        ...\n\nI'll look at the other occurrences and see if we can reuse code\nas well.\n\nThanks,\nStefan\n"},{"id":"308395","messageId":"CAGZ79kYU3yKrMwQp96-wdHedwzX7tKE1NK2OwdfOau1U3q1gaA@mail.gmail.com","threadId":"44745","inReplyTo":"xmqqinq6cgxa.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCHv5 1/4] submodule.h: add extern keyword to functions","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-27T17:59:30Z","receivedAt":"2016-12-27T18:08:21Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Dec 26, 2016 at 5:13 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Stefan Beller <sbeller@google.com> writes:\n>\n>> As the upcoming series will add a lot of functions to the submodule\n>> header, let's first make the header consistent to the rest of the project\n>> by adding the extern keyword to functions.\n>\n> This may be the right thing to do in the longer term but a patch\n> like this adds a lot of unnecessary work on the integrator when\n> there are _other_ topics that are in flight.  I'll see how bad the\n> conflicts are while merging the topic to 'pu'.\n>\n> Thanks.\n\nMaybe we can roll this patch on its own,\nso in case the latter patches need rerolls we still have this one and can\nbuild on top?\n\nThanks,\nStefan\n"},{"id":"308396","messageId":"CAGZ79kaRY0x+31-UiiBU1iBXGKAgeTRSSjvN0isd7jdg-Y7_rQ@mail.gmail.com","threadId":"44745","inReplyTo":"xmqqmvfich2e.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCHv5 4/4] rm: absorb a submodules git dir before deletion","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-27T18:17:33Z","receivedAt":"2016-12-27T18:18:20Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Dec 26, 2016 at 5:10 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Stefan Beller <sbeller@google.com> writes:\n>\n>> @@ -342,6 +313,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>>                       exit(0);\n>>       }\n>>\n>> +     submodules_absorb_gitdir_if_needed(prefix);\n>> +\n>>       /*\n>>        * If not forced, the file, the index and the HEAD (if exists)\n>>        * must match; but the file can already been removed, since\n>> @@ -358,9 +331,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>>                       oidclr(&oid);\n>>               if (check_local_mod(&oid, index_only))\n>>                       exit(1);\n>> -     } else if (!index_only) {\n>> -             if (check_submodules_use_gitfiles())\n>> -                     exit(1);\n>>       }\n>>\n>\n> Hmph.  It may be a bit strange to see an \"index-only\" remove to\n> touch working tree, no?  Yet submodules_absorb_gitdir_if_needed() is\n> unconditionally called above, which feels somewhat unexpected.\n\nI agree. In a reroll I'll protect the call with\n\n    if (!index_only)\n        submodules_absorb_gitdir_if_needed(prefix);\n\n>\n>> @@ -389,32 +359,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>>        */\n>>       if (!index_only) {\n>>               int removed = 0, gitmodules_modified = 0;\n>>               for (i = 0; i < list.nr; i++) {\n>>                       const char *path = list.entry[i].name;\n>>                       if (list.entry[i].is_submodule) {\n>> +                             struct strbuf buf = STRBUF_INIT;\n>> +\n>> +                             strbuf_addstr(&buf, path);\n>> +                             if (remove_dir_recursively(&buf, 0))\n>> +                                     die(_(\"could not remove '%s'\"), path);\n>> +                             strbuf_release(&buf);\n>> +\n>> +                             removed = 1;\n>> +                             if (!remove_path_from_gitmodules(path))\n>> +                                     gitmodules_modified = 1;\n>> +                             continue;\n>>                       }\n>\n> I do not see any behaviour change from the original (not quoted\n> here), but it is somewhat surprising that \"git rm ./submodule\" does\n> not really check if the submodule has local modifications and files\n> that are not even added before remove_dir_recursively() is called.\n>\n> Or perhaps I am reading the code incorrectly and such a check is\n> done elsewhere?\n\nWhen determining if the entry is a submodule (i.e. setting\nthe is_submodule bit) we have\n\n    list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode);\n    if (list.entry[list.nr++].is_submodule &&\n      !is_staging_gitmodules_ok())\n        die (_(\"Please stage ...\"));\n\nI think for clarity we could drop the is_submodule bit and only\nand directly do\n\n    if (S_ISGITLINK(ce->ce_mode) &&\n      !is_staging_gitmodules_ok())\n        die (_(\"Please stage ...\"));\n\nand below (the code that you quoted) also just check via\n    S_ISGITLINK(ce->ce_mode)\n\nThanks,\nStefan\n"},{"id":"308398","messageId":"CAGZ79kZLk2bNj2Z_PFfo9KzODn8nSihDLdLuKvpSPodR9Eg-4w@mail.gmail.com","threadId":"44745","inReplyTo":"CAGZ79kaRY0x+31-UiiBU1iBXGKAgeTRSSjvN0isd7jdg-Y7_rQ@mail.gmail.com","subject":"Re: [PATCHv5 4/4] rm: absorb a submodules git dir before deletion","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-27T18:26:37Z","receivedAt":"2016-12-27T18:26:44Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Dec 27, 2016 at 10:17 AM, Stefan Beller <sbeller@google.com> wrote:\n> On Mon, Dec 26, 2016 at 5:10 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Stefan Beller <sbeller@google.com> writes:\n>>\n>>> @@ -342,6 +313,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>>>                       exit(0);\n>>>       }\n>>>\n>>> +     submodules_absorb_gitdir_if_needed(prefix);\n>>> +\n>>>       /*\n>>>        * If not forced, the file, the index and the HEAD (if exists)\n>>>        * must match; but the file can already been removed, since\n>>> @@ -358,9 +331,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>>>                       oidclr(&oid);\n>>>               if (check_local_mod(&oid, index_only))\n>>>                       exit(1);\n>>> -     } else if (!index_only) {\n>>> -             if (check_submodules_use_gitfiles())\n>>> -                     exit(1);\n>>>       }\n>>>\n>>\n>> Hmph.  It may be a bit strange to see an \"index-only\" remove to\n>> touch working tree, no?  Yet submodules_absorb_gitdir_if_needed() is\n>> unconditionally called above, which feels somewhat unexpected.\n>\n> I agree. In a reroll I'll protect the call with\n>\n>     if (!index_only)\n>         submodules_absorb_gitdir_if_needed(prefix);\n>\n>>\n>>> @@ -389,32 +359,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>>>        */\n>>>       if (!index_only) {\n>>>               int removed = 0, gitmodules_modified = 0;\n>>>               for (i = 0; i < list.nr; i++) {\n>>>                       const char *path = list.entry[i].name;\n>>>                       if (list.entry[i].is_submodule) {\n>>> +                             struct strbuf buf = STRBUF_INIT;\n>>> +\n>>> +                             strbuf_addstr(&buf, path);\n>>> +                             if (remove_dir_recursively(&buf, 0))\n>>> +                                     die(_(\"could not remove '%s'\"), path);\n>>> +                             strbuf_release(&buf);\n>>> +\n>>> +                             removed = 1;\n>>> +                             if (!remove_path_from_gitmodules(path))\n>>> +                                     gitmodules_modified = 1;\n>>> +                             continue;\n>>>                       }\n>>\n>> I do not see any behaviour change from the original (not quoted\n>> here), but it is somewhat surprising that \"git rm ./submodule\" does\n>> not really check if the submodule has local modifications and files\n>> that are not even added before remove_dir_recursively() is called.\n>>\n>> Or perhaps I am reading the code incorrectly and such a check is\n>> done elsewhere?\n>\n> When determining if the entry is a submodule (i.e. setting\n> the is_submodule bit) we have\n>\n>     list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode);\n>     if (list.entry[list.nr++].is_submodule &&\n>       !is_staging_gitmodules_ok())\n>         die (_(\"Please stage ...\"));\n>\n\nWell scratch that.\nis_staging_gitmodules_ok only checks for the .gitmodules file and not\nfor the submodule itself (the submodule is not an argument to that function)\n\nThe actual answer is found in check_local_mod called via\n\n    if (!force) {\n        struct object_id oid;\n        if (get_oid(\"HEAD\", &oid))\n            oidclr(&oid);\n        if (check_local_mod(&oid, index_only))\n            exit(1);\n    }\n\ncheck_local_mod was touched in the previous patch as it has:\n\n    /*\n    * Is the index different from the file in the work tree?\n    * If it's a submodule, is its work tree modified?\n    */\n    if (ce_match_stat(ce, &st, 0) ||\n      (S_ISGITLINK(ce->ce_mode) &&\n       bad_to_remove_submodule(ce->name,\n            SUBMODULE_REMOVAL_DIE_ON_ERROR |\n            SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED)))\n                local_changes = 1;\n"},{"id":"308420","messageId":"xmqqeg0t6nqj.fsf@gitster.mtv.corp.google.com","threadId":"44745","inReplyTo":"CAGZ79kZLk2bNj2Z_PFfo9KzODn8nSihDLdLuKvpSPodR9Eg-4w@mail.gmail.com","subject":"Re: [PATCHv5 4/4] rm: absorb a submodules git dir before deletion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-12-27T21:55:32Z","receivedAt":"2016-12-27T21:56:27Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n>>>> @@ -358,9 +331,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>>>>                       oidclr(&oid);\n>>>>               if (check_local_mod(&oid, index_only))\n>>>>                       exit(1);\n>>>> -     } else if (!index_only) {\n>>>> -             if (check_submodules_use_gitfiles())\n>>>> -                     exit(1);\n>>>>       }\n>>>>\n>>>\n>>> Hmph.  It may be a bit strange to see an \"index-only\" remove to\n>>> touch working tree, no?  Yet submodules_absorb_gitdir_if_needed() is\n>>> unconditionally called above, which feels somewhat unexpected.\n>> ...\n> Well scratch that.\n> is_staging_gitmodules_ok only checks for the .gitmodules file and not\n> for the submodule itself (the submodule is not an argument to that function)\n>\n> The actual answer is found in check_local_mod called via\n>\n>     if (!force) {\n>         struct object_id oid;\n>         if (get_oid(\"HEAD\", &oid))\n>             oidclr(&oid);\n>         if (check_local_mod(&oid, index_only))\n>             exit(1);\n>     }\n\nOK, that happens way before this loop starts finding submodules and\nremoving them, so we can say that the latter is well protected.\n\nThanks.\n"}]}